Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

inital commit

  • Loading branch information...
commit 211989b9c7ab69eab215e7c2d13f605af2e3a2f2 0 parents
@rkh authored
2  Gemfile
@@ -0,0 +1,2 @@
+source :rubygems
+gemspec
65 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 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 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 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 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  lib/tool/version.rb
@@ -0,0 +1,3 @@
+module Tool
+ VERSION = '0.1.0'
+end
6 spec/autoloader/foo.rb
@@ -0,0 +1,6 @@
+module Autoloader
+ module Foo
+ Answer = 42
+ include Tool::Autoloader
+ end
+end
3  spec/autoloader/foo/answer.rb
@@ -0,0 +1,3 @@
+module Autoloader::Foo
+ Answer = 23
+end
2  spec/autoloader/foo/bar.rb
@@ -0,0 +1,2 @@
+class Autoloader::Foo::Bar
+end
2  spec/autoloader/foo/baz/bar.rb
@@ -0,0 +1,2 @@
+class Autoloader::Foo::Baz::Bar
+end
2  spec/autoloader/foo/baz/foo.rb
@@ -0,0 +1,2 @@
+module Autoloader::Foo::Baz::Foo
+end
2  spec/autoloader/foo/blah.rb
@@ -0,0 +1,2 @@
+class Autoloader::Foo::Blah
+end
2  spec/autoloader/foo/blah/boom.rb
@@ -0,0 +1,2 @@
+class Autoloader::Foo::Blah::Boom
+end
2  spec/autoloader/foo/cli.rb
@@ -0,0 +1,2 @@
+class Autoloader::Foo::CLI
+end
2  spec/autoloader/foo/foo_bar.rb
@@ -0,0 +1,2 @@
+class Autoloader::Foo::FooBar
+end
3  spec/autoloader/foo/version.rb
@@ -0,0 +1,3 @@
+module Autoloader::Foo
+ VERSION = 1337
+end
47 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 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
91 spec/tool_spec.rb
@@ -0,0 +1,91 @@
+require 'tool'
+
+describe Tool do
+ describe :set do
+ let(:object) { Object.new }
+
+ it 'defines a getter' do
+ Tool.set(object, :foo, :bar)
+ object.foo.should be == :bar
+ end
+
+ it 'defines a setter' do
+ Tool.set(object, :foo, :bar)
+ object.foo = :foo
+ object.foo.should be == :foo
+ end
+
+ it 'defines a flag' do
+ Tool.set(object, :foo, :bar)
+ object.should be_foo
+ object.foo = false
+ object.should_not be_foo
+ end
+
+ it 'takes a block' do
+ Tool.set(object, :foo) { :bar }
+ object.foo.should be == :bar
+ object.should be_foo
+ end
+
+ it 'adds a setter, even with a block' do
+ Tool.set(object, :foo) { :bar }
+ object.foo = false
+ object.should_not be_foo
+ end
+
+ it 'uses the blocks value as flag' do
+ Tool.set(object, :foo) { false }
+ object.should_not be_foo
+ end
+
+ it 'runs the block more than once' do
+ counter = 0
+ Tool.set(object, :foo) { counter += 1 }
+ object.foo.should be == 1
+ object.foo.should be == 2
+ end
+
+ it 'wraps a block passed to the setter' do
+ Tool.set(object, :foo) { :bar }
+ object.foo = proc { false }
+ object.should be_foo
+ object.foo.should be_a(Proc)
+ end
+
+ it 'allows passing in a hash' do
+ Tool.set(object, :foo => :bar)
+ object.foo.should be == :bar
+ end
+
+ it 'allows passing in nil' do
+ Tool.set(object, :foo, nil)
+ object.foo.should be_nil
+ end
+
+ it 'does not use the passed block if nil is given' do
+ Tool.set(object, :foo, nil) { :bar }
+ object.foo.should be_nil
+ object.should_not be_foo
+ end
+
+ it 'inherits class settings' do
+ a = Class.new
+ b = Class.new(a)
+
+ Tool.set(a, :foo, :bar)
+ b.foo.should be == :bar
+ end
+
+ it 'allows overriding class settings in subclasses' do
+ a = Class.new
+ b = Class.new(a)
+
+ Tool.set(a, :foo, :bar)
+ b.foo = :foo
+
+ a.foo.should be == :bar
+ b.foo.should be == :foo
+ end
+ end
+end
16 tool.gemspec
@@ -0,0 +1,16 @@
+$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
+require 'tool/version'
+
+Gem::Specification.new 'tool', Tool::VERSION do |s|
+ s.description = "This is basically code usually copy from one project to another"
+ s.summary = "Sane tools for Ruby without monkey-patching"
+
+ s.authors = ["Konstantin Haase"]
+ s.email = "konstantin.mailinglists@googlemail.com"
+ s.homepage = "http://github.com/rkh/tool"
+
+ s.files = `git ls-files`.split("\n") - %w[Gemfile .gitignore .travis.yml]
+ s.test_files = s.files.select { |p| p =~ /^spec\/.*_spec.rb/ }
+
+ s.add_development_dependency 'rspec', '~> 2.7'
+end
Please sign in to comment.
Something went wrong with that request. Please try again.