tool 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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