tool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ Sane tools for Ruby without monkey-patching. This is basically code usually
2
+ copy from one project to another.
3
+
4
+ Goal of this library is to be lightweight and unobtrusive, so you don't have
5
+ to feel guilty for using it. Mixins are lazy-loaded.
6
+
7
+ # Included Tools
8
+
9
+ ## Tool::Autoloader
10
+
11
+ Sets up `autoload` directives for nested constants. Has the advantage of
12
+ setting these up when included instead of hooking into `const_missing`, like
13
+ ActiveSupport does. The means it is fast, transparent, and does not alter
14
+ constant lookup in any way.
15
+
16
+ ``` ruby
17
+ module Foo
18
+ include Tool::Autoloader
19
+ end
20
+ ```
21
+
22
+ If you don't want to include the module, use `setup`:
23
+
24
+ ``` ruby
25
+ Tool::Autoloader.setup Foo
26
+ ```
27
+
28
+ ## Tool::Lock
29
+
30
+ Adds a `synchronize` method that behaves like `Rubinius.synchronize(self)`,
31
+ i.e. recursively going through the lock will not result in a deadlock:
32
+
33
+ ``` ruby
34
+ class Foo
35
+ include Tool::Lock
36
+
37
+ def recursive_fun(i = 0)
38
+ return i if i == 5
39
+ # THIS NEEDS TO BE THREAD-SAFE!!!
40
+ synchronize { recursive_fun(i + 1) }
41
+ end
42
+ end
43
+ ```
44
+
45
+ It will use `Rubinius.synchronize` when on Rubinius.
46
+
47
+ ## Tool.set
48
+
49
+ Simplified version of Sinatra's set:
50
+
51
+ ``` ruby
52
+ class Foo
53
+ Tool.set(self, :foo, :foo)
54
+ end
55
+
56
+ class Bar < Foo
57
+ end
58
+
59
+ Bar.foo # => :foo
60
+
61
+ Bar.foo = :bar
62
+ Bar.foo # => :bar
63
+
64
+ Foo.foo # => :foo
65
+ ```
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+ require 'tool/version'
3
+
4
+ def gem(*args) sh("gem", *args.map(&:to_s)) end
5
+ def git(*args) sh("git", *args.map(&:to_s)) end
6
+
7
+ gem_file = "tool-#{Tool::VERSION}.gem"
8
+ version = "v#{Tool::VERSION}"
9
+ message = "Release #{version}"
10
+
11
+ task(:spec) { ruby "-S rspec spec" }
12
+ task(:build) { gem :build, 'tool.gemspec' }
13
+ task(:install => :build) { gem :install, gem_file }
14
+ task(:publish => :install) { gem :push, gem_file }
15
+ task(:commit) { git :commit, '--allow-empty', '-m', message }
16
+ task(:tag) { git :tag, '-s', '-m', message, version }
17
+ task(:push) { git :push }
18
+
19
+ task :release => [:spec, :commit, :publish, :tag, :push]
20
+ task :default => :spec
21
+ task :test => :spec
data/lib/tool.rb ADDED
@@ -0,0 +1,17 @@
1
+ module Tool
2
+ autoload :Autoloader, 'tool/autoloader'
3
+ autoload :Lock, 'tool/lock'
4
+ autoload :VERSION, 'tool/version'
5
+
6
+ def self.set(object, key, value = (no_value = true), &block)
7
+ return key.each_pair { |k,v| set(object, k, v) } if no_value and not block
8
+
9
+ block = proc { value } unless no_value
10
+ sclass = (class << object; self; end)
11
+ setter = self
12
+
13
+ sclass.send(:define_method, key, &block)
14
+ sclass.send(:define_method, "#{key}=") { |v| setter.set(self, key, v) }
15
+ sclass.send(:define_method, "#{key}?") { !!__send__(key) }
16
+ end
17
+ end
@@ -0,0 +1,60 @@
1
+ module Tool
2
+ module Autoloader
3
+ CAPITALIZE = %w[version cli] unless defined? CAPITALIZE
4
+
5
+ def self.setup(container, path = caller_dir)
6
+ prefix = path_for(container)
7
+ full_prefix = path_for(container, true)
8
+ path = File.expand_path(prefix, path)
9
+ directories = []
10
+
11
+ Dir.glob("#{path}/*") do |file|
12
+ base = File.basename(file, '.rb')
13
+ const = constant_for(base)
14
+ lib = "#{full_prefix}/#{base}"
15
+
16
+ if File.directory? file
17
+ directories << const
18
+ elsif file.end_with? '.rb'
19
+ container.autoload const, lib
20
+ end
21
+ end
22
+
23
+ directories.each do |const|
24
+ next if container.const_defined? const
25
+ nested = container.const_set(const, Module.new)
26
+ setup nested, path
27
+ end
28
+ end
29
+
30
+ def self.path_for(constant, full = false)
31
+ name = constant.name.dup
32
+ name = name[/[^:]+$/] unless full
33
+ name.gsub! /([A-Z]+)([A-Z][a-z])/,'\1_\2'
34
+ name.gsub! /([a-z\d])([A-Z])/,'\1_\2'
35
+ name.gsub! '::', '/'
36
+ name.tr("-", "_").downcase
37
+ end
38
+
39
+ def self.constant_for(file)
40
+ return file.upcase if CAPITALIZE.include? file
41
+ file.split('.', 2).first.split(/[_-]/).map(&:capitalize).join
42
+ end
43
+
44
+ def self.capitalize(*args)
45
+ CAPITALIZE.concat(args)
46
+ end
47
+
48
+ def self.append_features(base)
49
+ setup(base)
50
+ end
51
+
52
+ def self.caller_dir
53
+ caller.each do |line|
54
+ file = File.expand_path(line.split(':', 2).first)
55
+ return File.dirname(file) if file != File.expand_path(__FILE__)
56
+ end
57
+ File.dirname($0)
58
+ end
59
+ end
60
+ end
data/lib/tool/lock.rb ADDED
@@ -0,0 +1,33 @@
1
+ module Tool
2
+ module Lock
3
+ if defined? Rubinius
4
+ def synchronize
5
+ Rubinius.synchronize(self) { yield }
6
+ end
7
+ else
8
+ require 'thread'
9
+ @lock = Mutex.new
10
+
11
+ def self.synchronize(&block)
12
+ @lock.synchronize(&block)
13
+ end
14
+
15
+ def synchronize(&block)
16
+ Lock.synchronize { @lock, @locked_by = Mutex.new, nil unless lock? } unless lock?
17
+ return yield if @locked_by == Thread.current
18
+ @lock.synchronize do
19
+ @locked_by = Thread.current
20
+ result = yield
21
+ @locked_by = nil
22
+ result
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def lock?
29
+ instance_variable_defined? :@lock
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module Tool
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,6 @@
1
+ module Autoloader
2
+ module Foo
3
+ Answer = 42
4
+ include Tool::Autoloader
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module Autoloader::Foo
2
+ Answer = 23
3
+ end
@@ -0,0 +1,2 @@
1
+ class Autoloader::Foo::Bar
2
+ end
@@ -0,0 +1,2 @@
1
+ class Autoloader::Foo::Baz::Bar
2
+ end
@@ -0,0 +1,2 @@
1
+ module Autoloader::Foo::Baz::Foo
2
+ end
@@ -0,0 +1,2 @@
1
+ class Autoloader::Foo::Blah
2
+ end
@@ -0,0 +1,2 @@
1
+ class Autoloader::Foo::Blah::Boom
2
+ end
@@ -0,0 +1,2 @@
1
+ class Autoloader::Foo::CLI
2
+ end
@@ -0,0 +1,2 @@
1
+ class Autoloader::Foo::FooBar
2
+ end
@@ -0,0 +1,3 @@
1
+ module Autoloader::Foo
2
+ VERSION = 1337
3
+ end
@@ -0,0 +1,47 @@
1
+ require 'tool'
2
+ require 'autoloader/foo'
3
+
4
+ describe Tool::Autoloader do
5
+ # poor man's matchers
6
+ def autoload(const) be_autoload(const) end
7
+ def start_with(str) be_start_with(str) end
8
+
9
+ it 'sets up autoloading' do
10
+ Autoloader::Foo.should autoload(:Bar)
11
+ Autoloader::Foo::Bar.name.should start_with("Autoloader::Foo::")
12
+ end
13
+
14
+ it 'creates modules for subdirectories' do
15
+ Autoloader::Foo.should_not autoload(:Baz)
16
+ Autoloader::Foo::Baz.should autoload(:Bar)
17
+ end
18
+
19
+ it 'handles nested constants with same name' do
20
+ Autoloader::Foo::Baz::Foo.should_not be == Autoloader::Foo
21
+ end
22
+
23
+ it 'does not automatically set up autoloading for autoloaded constants' do
24
+ Autoloader::Foo::Blah.should_not autoload(:Boom)
25
+ expect { Autoloader::Foo::Blah::Boom }.to raise_error(NameError)
26
+ end
27
+
28
+ it 'does not override existing constants' do
29
+ Autoloader::Foo::Answer.should be == 42
30
+ end
31
+
32
+ it 'loads VERSION' do
33
+ Autoloader::Foo.should autoload(:VERSION)
34
+ Autoloader::Foo.should_not autoload(:Version)
35
+ Autoloader::Foo::VERSION.should be == 1337
36
+ end
37
+
38
+ it 'loads CLI' do
39
+ Autoloader::Foo.should autoload(:CLI)
40
+ Autoloader::Foo.should_not autoload(:Cli)
41
+ end
42
+
43
+ it 'loads camel-cased constants' do
44
+ Autoloader::Foo.should autoload(:FooBar)
45
+ Autoloader::Foo.should_not autoload(:Foo_bar)
46
+ end
47
+ end
data/spec/lock_spec.rb ADDED
@@ -0,0 +1,43 @@
1
+ require 'tool'
2
+
3
+ describe Tool::Lock do
4
+ before do
5
+ Thread.abort_on_exception = true
6
+ end
7
+
8
+ let(:object) { Object.new.extend(Tool::Lock) }
9
+ let(:track) { [] }
10
+
11
+ def synchronize(&block)
12
+ object.synchronize(&block)
13
+ end
14
+
15
+ it 'runs the given block' do
16
+ synchronize { track << :ran }
17
+ track.should be == [:ran]
18
+ end
19
+
20
+ it 'locks for other threads' do
21
+ a = Thread.new { synchronize { sleep(0.1) and track << :first } }
22
+ b = Thread.new { synchronize { track << :second } }
23
+
24
+ a.join
25
+ b.join
26
+
27
+ track.should be == [:first, :second]
28
+ end
29
+
30
+ it 'is no global lock' do
31
+ a = Thread.new { Object.new.extend(Tool::Lock).synchronize { sleep(0.1) and track << :first } }
32
+ b = Thread.new { Object.new.extend(Tool::Lock).synchronize { track << :second } }
33
+
34
+ a.join
35
+ b.join
36
+
37
+ track.should be == [:second, :first]
38
+ end
39
+
40
+ it 'has no issue with recursion' do
41
+ synchronize { synchronize { } }
42
+ end
43
+ end
data/spec/tool_spec.rb ADDED
@@ -0,0 +1,91 @@
1
+ require 'tool'
2
+
3
+ describe Tool do
4
+ describe :set do
5
+ let(:object) { Object.new }
6
+
7
+ it 'defines a getter' do
8
+ Tool.set(object, :foo, :bar)
9
+ object.foo.should be == :bar
10
+ end
11
+
12
+ it 'defines a setter' do
13
+ Tool.set(object, :foo, :bar)
14
+ object.foo = :foo
15
+ object.foo.should be == :foo
16
+ end
17
+
18
+ it 'defines a flag' do
19
+ Tool.set(object, :foo, :bar)
20
+ object.should be_foo
21
+ object.foo = false
22
+ object.should_not be_foo
23
+ end
24
+
25
+ it 'takes a block' do
26
+ Tool.set(object, :foo) { :bar }
27
+ object.foo.should be == :bar
28
+ object.should be_foo
29
+ end
30
+
31
+ it 'adds a setter, even with a block' do
32
+ Tool.set(object, :foo) { :bar }
33
+ object.foo = false
34
+ object.should_not be_foo
35
+ end
36
+
37
+ it 'uses the blocks value as flag' do
38
+ Tool.set(object, :foo) { false }
39
+ object.should_not be_foo
40
+ end
41
+
42
+ it 'runs the block more than once' do
43
+ counter = 0
44
+ Tool.set(object, :foo) { counter += 1 }
45
+ object.foo.should be == 1
46
+ object.foo.should be == 2
47
+ end
48
+
49
+ it 'wraps a block passed to the setter' do
50
+ Tool.set(object, :foo) { :bar }
51
+ object.foo = proc { false }
52
+ object.should be_foo
53
+ object.foo.should be_a(Proc)
54
+ end
55
+
56
+ it 'allows passing in a hash' do
57
+ Tool.set(object, :foo => :bar)
58
+ object.foo.should be == :bar
59
+ end
60
+
61
+ it 'allows passing in nil' do
62
+ Tool.set(object, :foo, nil)
63
+ object.foo.should be_nil
64
+ end
65
+
66
+ it 'does not use the passed block if nil is given' do
67
+ Tool.set(object, :foo, nil) { :bar }
68
+ object.foo.should be_nil
69
+ object.should_not be_foo
70
+ end
71
+
72
+ it 'inherits class settings' do
73
+ a = Class.new
74
+ b = Class.new(a)
75
+
76
+ Tool.set(a, :foo, :bar)
77
+ b.foo.should be == :bar
78
+ end
79
+
80
+ it 'allows overriding class settings in subclasses' do
81
+ a = Class.new
82
+ b = Class.new(a)
83
+
84
+ Tool.set(a, :foo, :bar)
85
+ b.foo = :foo
86
+
87
+ a.foo.should be == :bar
88
+ b.foo.should be == :foo
89
+ end
90
+ end
91
+ end
data/tool.gemspec ADDED
@@ -0,0 +1,16 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+ require 'tool/version'
3
+
4
+ Gem::Specification.new 'tool', Tool::VERSION do |s|
5
+ s.description = "This is basically code usually copy from one project to another"
6
+ s.summary = "Sane tools for Ruby without monkey-patching"
7
+
8
+ s.authors = ["Konstantin Haase"]
9
+ s.email = "konstantin.mailinglists@googlemail.com"
10
+ s.homepage = "http://github.com/rkh/tool"
11
+
12
+ s.files = `git ls-files`.split("\n") - %w[Gemfile .gitignore .travis.yml]
13
+ s.test_files = s.files.select { |p| p =~ /^spec\/.*_spec.rb/ }
14
+
15
+ s.add_development_dependency 'rspec', '~> 2.7'
16
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tool
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Konstantin Haase
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-27 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &2152929520 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.7'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2152929520
25
+ description: This is basically code usually copy from one project to another
26
+ email: konstantin.mailinglists@googlemail.com
27
+ executables: []
28
+ extensions: []
29
+ extra_rdoc_files: []
30
+ files:
31
+ - README.md
32
+ - Rakefile
33
+ - lib/tool.rb
34
+ - lib/tool/autoloader.rb
35
+ - lib/tool/lock.rb
36
+ - lib/tool/version.rb
37
+ - spec/autoloader/foo.rb
38
+ - spec/autoloader/foo/answer.rb
39
+ - spec/autoloader/foo/bar.rb
40
+ - spec/autoloader/foo/baz/bar.rb
41
+ - spec/autoloader/foo/baz/foo.rb
42
+ - spec/autoloader/foo/blah.rb
43
+ - spec/autoloader/foo/blah/boom.rb
44
+ - spec/autoloader/foo/cli.rb
45
+ - spec/autoloader/foo/foo_bar.rb
46
+ - spec/autoloader/foo/version.rb
47
+ - spec/autoloader_spec.rb
48
+ - spec/lock_spec.rb
49
+ - spec/tool_spec.rb
50
+ - tool.gemspec
51
+ homepage: http://github.com/rkh/tool
52
+ licenses: []
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 1.8.10
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Sane tools for Ruby without monkey-patching
75
+ test_files:
76
+ - spec/autoloader_spec.rb
77
+ - spec/lock_spec.rb
78
+ - spec/tool_spec.rb