Skip to content

Commit

Permalink
inital commit
Browse files Browse the repository at this point in the history
  • Loading branch information
rkh committed Oct 27, 2011
0 parents commit 211989b
Show file tree
Hide file tree
Showing 21 changed files with 424 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Gemfile
@@ -0,0 +1,2 @@
source :rubygems
gemspec
65 changes: 65 additions & 0 deletions README.md
@@ -0,0 +1,65 @@
Sane tools for Ruby without monkey-patching. This is basically code usually
copy from one project to another.

Goal of this library is to be lightweight and unobtrusive, so you don't have
to feel guilty for using it. Mixins are lazy-loaded.

# Included Tools

## Tool::Autoloader

Sets up `autoload` directives for nested constants. Has the advantage of
setting these up when included instead of hooking into `const_missing`, like
ActiveSupport does. The means it is fast, transparent, and does not alter
constant lookup in any way.

``` ruby
module Foo
include Tool::Autoloader
end
```

If you don't want to include the module, use `setup`:

``` ruby
Tool::Autoloader.setup Foo
```

## Tool::Lock

Adds a `synchronize` method that behaves like `Rubinius.synchronize(self)`,
i.e. recursively going through the lock will not result in a deadlock:

``` ruby
class Foo
include Tool::Lock

def recursive_fun(i = 0)
return i if i == 5
# THIS NEEDS TO BE THREAD-SAFE!!!
synchronize { recursive_fun(i + 1) }
end
end
```

It will use `Rubinius.synchronize` when on Rubinius.

## Tool.set

Simplified version of Sinatra's set:

``` ruby
class Foo
Tool.set(self, :foo, :foo)
end

class Bar < Foo
end

Bar.foo # => :foo

Bar.foo = :bar
Bar.foo # => :bar

Foo.foo # => :foo
```
21 changes: 21 additions & 0 deletions Rakefile
@@ -0,0 +1,21 @@
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
require 'tool/version'

def gem(*args) sh("gem", *args.map(&:to_s)) end
def git(*args) sh("git", *args.map(&:to_s)) end

gem_file = "tool-#{Tool::VERSION}.gem"
version = "v#{Tool::VERSION}"
message = "Release #{version}"

task(:spec) { ruby "-S rspec spec" }
task(:build) { gem :build, 'tool.gemspec' }
task(:install => :build) { gem :install, gem_file }
task(:publish => :install) { gem :push, gem_file }
task(:commit) { git :commit, '--allow-empty', '-m', message }
task(:tag) { git :tag, '-s', '-m', message, version }
task(:push) { git :push }

task :release => [:spec, :commit, :publish, :tag, :push]
task :default => :spec
task :test => :spec
17 changes: 17 additions & 0 deletions lib/tool.rb
@@ -0,0 +1,17 @@
module Tool
autoload :Autoloader, 'tool/autoloader'
autoload :Lock, 'tool/lock'
autoload :VERSION, 'tool/version'

def self.set(object, key, value = (no_value = true), &block)
return key.each_pair { |k,v| set(object, k, v) } if no_value and not block

block = proc { value } unless no_value
sclass = (class << object; self; end)
setter = self

sclass.send(:define_method, key, &block)
sclass.send(:define_method, "#{key}=") { |v| setter.set(self, key, v) }
sclass.send(:define_method, "#{key}?") { !!__send__(key) }
end
end
60 changes: 60 additions & 0 deletions lib/tool/autoloader.rb
@@ -0,0 +1,60 @@
module Tool
module Autoloader
CAPITALIZE = %w[version cli] unless defined? CAPITALIZE

def self.setup(container, path = caller_dir)
prefix = path_for(container)
full_prefix = path_for(container, true)
path = File.expand_path(prefix, path)
directories = []

Dir.glob("#{path}/*") do |file|
base = File.basename(file, '.rb')
const = constant_for(base)
lib = "#{full_prefix}/#{base}"

if File.directory? file
directories << const
elsif file.end_with? '.rb'
container.autoload const, lib
end
end

directories.each do |const|
next if container.const_defined? const
nested = container.const_set(const, Module.new)
setup nested, path
end
end

def self.path_for(constant, full = false)
name = constant.name.dup
name = name[/[^:]+$/] unless full
name.gsub! /([A-Z]+)([A-Z][a-z])/,'\1_\2'
name.gsub! /([a-z\d])([A-Z])/,'\1_\2'
name.gsub! '::', '/'
name.tr("-", "_").downcase
end

def self.constant_for(file)
return file.upcase if CAPITALIZE.include? file
file.split('.', 2).first.split(/[_-]/).map(&:capitalize).join
end

def self.capitalize(*args)
CAPITALIZE.concat(args)
end

def self.append_features(base)
setup(base)
end

def self.caller_dir
caller.each do |line|
file = File.expand_path(line.split(':', 2).first)
return File.dirname(file) if file != File.expand_path(__FILE__)
end
File.dirname($0)
end
end
end
33 changes: 33 additions & 0 deletions lib/tool/lock.rb
@@ -0,0 +1,33 @@
module Tool
module Lock
if defined? Rubinius
def synchronize
Rubinius.synchronize(self) { yield }
end
else
require 'thread'
@lock = Mutex.new

def self.synchronize(&block)
@lock.synchronize(&block)
end

def synchronize(&block)
Lock.synchronize { @lock, @locked_by = Mutex.new, nil unless lock? } unless lock?
return yield if @locked_by == Thread.current
@lock.synchronize do
@locked_by = Thread.current
result = yield
@locked_by = nil
result
end
end

private

def lock?
instance_variable_defined? :@lock
end
end
end
end
3 changes: 3 additions & 0 deletions lib/tool/version.rb
@@ -0,0 +1,3 @@
module Tool
VERSION = '0.1.0'
end
6 changes: 6 additions & 0 deletions spec/autoloader/foo.rb
@@ -0,0 +1,6 @@
module Autoloader
module Foo
Answer = 42
include Tool::Autoloader
end
end
3 changes: 3 additions & 0 deletions spec/autoloader/foo/answer.rb
@@ -0,0 +1,3 @@
module Autoloader::Foo
Answer = 23
end
2 changes: 2 additions & 0 deletions spec/autoloader/foo/bar.rb
@@ -0,0 +1,2 @@
class Autoloader::Foo::Bar
end
2 changes: 2 additions & 0 deletions spec/autoloader/foo/baz/bar.rb
@@ -0,0 +1,2 @@
class Autoloader::Foo::Baz::Bar
end
2 changes: 2 additions & 0 deletions spec/autoloader/foo/baz/foo.rb
@@ -0,0 +1,2 @@
module Autoloader::Foo::Baz::Foo
end
2 changes: 2 additions & 0 deletions spec/autoloader/foo/blah.rb
@@ -0,0 +1,2 @@
class Autoloader::Foo::Blah
end
2 changes: 2 additions & 0 deletions spec/autoloader/foo/blah/boom.rb
@@ -0,0 +1,2 @@
class Autoloader::Foo::Blah::Boom
end
2 changes: 2 additions & 0 deletions spec/autoloader/foo/cli.rb
@@ -0,0 +1,2 @@
class Autoloader::Foo::CLI
end
2 changes: 2 additions & 0 deletions spec/autoloader/foo/foo_bar.rb
@@ -0,0 +1,2 @@
class Autoloader::Foo::FooBar
end
3 changes: 3 additions & 0 deletions spec/autoloader/foo/version.rb
@@ -0,0 +1,3 @@
module Autoloader::Foo
VERSION = 1337
end
47 changes: 47 additions & 0 deletions spec/autoloader_spec.rb
@@ -0,0 +1,47 @@
require 'tool'
require 'autoloader/foo'

describe Tool::Autoloader do
# poor man's matchers
def autoload(const) be_autoload(const) end
def start_with(str) be_start_with(str) end

it 'sets up autoloading' do
Autoloader::Foo.should autoload(:Bar)
Autoloader::Foo::Bar.name.should start_with("Autoloader::Foo::")
end

it 'creates modules for subdirectories' do
Autoloader::Foo.should_not autoload(:Baz)
Autoloader::Foo::Baz.should autoload(:Bar)
end

it 'handles nested constants with same name' do
Autoloader::Foo::Baz::Foo.should_not be == Autoloader::Foo
end

it 'does not automatically set up autoloading for autoloaded constants' do
Autoloader::Foo::Blah.should_not autoload(:Boom)
expect { Autoloader::Foo::Blah::Boom }.to raise_error(NameError)
end

it 'does not override existing constants' do
Autoloader::Foo::Answer.should be == 42
end

it 'loads VERSION' do
Autoloader::Foo.should autoload(:VERSION)
Autoloader::Foo.should_not autoload(:Version)
Autoloader::Foo::VERSION.should be == 1337
end

it 'loads CLI' do
Autoloader::Foo.should autoload(:CLI)
Autoloader::Foo.should_not autoload(:Cli)
end

it 'loads camel-cased constants' do
Autoloader::Foo.should autoload(:FooBar)
Autoloader::Foo.should_not autoload(:Foo_bar)
end
end
43 changes: 43 additions & 0 deletions spec/lock_spec.rb
@@ -0,0 +1,43 @@
require 'tool'

describe Tool::Lock do
before do
Thread.abort_on_exception = true
end

let(:object) { Object.new.extend(Tool::Lock) }
let(:track) { [] }

def synchronize(&block)
object.synchronize(&block)
end

it 'runs the given block' do
synchronize { track << :ran }
track.should be == [:ran]
end

it 'locks for other threads' do
a = Thread.new { synchronize { sleep(0.1) and track << :first } }
b = Thread.new { synchronize { track << :second } }

a.join
b.join

track.should be == [:first, :second]
end

it 'is no global lock' do
a = Thread.new { Object.new.extend(Tool::Lock).synchronize { sleep(0.1) and track << :first } }
b = Thread.new { Object.new.extend(Tool::Lock).synchronize { track << :second } }

a.join
b.join

track.should be == [:second, :first]
end

it 'has no issue with recursion' do
synchronize { synchronize { } }
end
end

0 comments on commit 211989b

Please sign in to comment.