tool-0.2.3/0000755000175000017500000000000013122004751013752 5ustar balasankarcbalasankarctool-0.2.3/.gitignore0000644000175000017500000000002613122004751015740 0ustar balasankarcbalasankarcGemfile.lock .coveragetool-0.2.3/tool.gemspec0000644000175000017500000000206013122004751016272 0ustar balasankarcbalasankarc$:.unshift File.expand_path("../lib", __FILE__) require "tool/version" Gem::Specification.new do |s| s.name = "tool" s.version = Tool::VERSION s.author = "Konstantin Haase" s.email = "konstantin.mailinglists@googlemail.com" s.homepage = "https://github.com/rkh/tool" s.summary = %q{general purpose library} s.description = %q{general purpose Ruby library used by Sinatra 2.0, Mustermann and related projects} s.license = 'MIT' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.extra_rdoc_files = `git ls-files -- *.md`.split("\n") s.required_ruby_version = '>= 2.0.0' s.add_development_dependency 'rspec', '~> 3.0.0.beta' s.add_development_dependency 'simplecov' s.add_development_dependency 'coveralls' s.add_development_dependency "rake" end tool-0.2.3/Gemfile0000644000175000017500000000004613122004751015245 0ustar balasankarcbalasankarcsource 'https://rubygems.org' gemspec tool-0.2.3/README.md0000644000175000017500000000367013122004751015237 0ustar balasankarcbalasankarc*Make sure you view the correct docs: [latest release](http://rubydoc.info/gems/tool/frames), [master](http://rubydoc.info/github/rkh/tool/master/frames).* Welcome to [Tool](http://www.youtube.com/watch?v=mYKLvYGqaC0), the general purpose Ruby library used by Sinatra 2.0, Mustermann and related projects. ## Tool::Decoration Mixin for easy method decorations. ``` ruby class Frank extend Tool::Decoration def self.get(path, &block) decorate(block) do |method| puts "mapping GET #{path} to #{method}" end end end class MyApp < Frank get '/hi' do "Hello World" end get '/'; get '/index.php' def index "This is the index page." end end ``` ## Tool::EqualityMap Weak reference caching based on key equality. Used for caching. ``` ruby class ExpensiveComputation @map = Tool::EqualityMap.new def self.new(*args) @map.fetch(*args) { super } end end ``` Note that `fetch` is not guaranteed to return the object, even if it has not been garbage collected yet, especially when used concurrently. Therefore, the block passed to `fetch` has to be idempotent. ## Tool::ThreadLocal Have thread local values without them actually being thread global. Advantages: * Values for all threads are garbage collected when ThreadLocal instance is. * Values for specific thread are garbage collected when thread is. * No hidden global state. * Supports other data types besides hashes. ``` ruby local = Tool::ThreadLocal.new local[:key] = "value" Thread.new do local[:key] = "other value" puts local[:key] # other value end.join puts local[:key] # value ``` Usage with a pre-filled array: ``` ruby local = Tool::ThreadLocal.new([:foo]) local << :bar Thread.new { p local }.join # [:foo] p local # [:foo, :bar] ``` ## Tool::WarningFilter Enables Ruby's built-in warnings (-w) but filters out those caused by third-party gems. Does not invlove any manual set up. ``` ruby require 'tool/warning_filter' Foo = 10 Foo = 20 ```tool-0.2.3/spec/0000755000175000017500000000000013122004751014704 5ustar balasankarcbalasankarctool-0.2.3/spec/equality_map_spec.rb0000644000175000017500000000116613122004751020741 0ustar balasankarcbalasankarcrequire 'tool/equality_map' describe Tool::EqualityMap do before { GC.disable } after { GC.enable } describe :fetch do subject { Tool::EqualityMap.new } specify 'with existing entry' do next if subject.is_a? Hash subject.fetch("foo") { "foo" } result = subject.fetch("foo") { "bar" } expect(result).to be == "foo" end specify 'with GC-removed entry' do next if subject.is_a? Hash subject.fetch("foo") { "foo" } expect(subject.map).to receive(:[]).and_return(nil) result = subject.fetch("foo") { "bar" } expect(result).to be == "bar" end end end tool-0.2.3/spec/support/0000755000175000017500000000000013122004751016420 5ustar balasankarcbalasankarctool-0.2.3/spec/support/coverage.rb0000644000175000017500000000054213122004751020541 0ustar balasankarcbalasankarcrequire 'tool/warning_filter' require 'simplecov' require 'coveralls' SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter ] SimpleCov.start do project_name 'tool' minimum_coverage 100 coverage_dir '.coverage' add_filter '/spec/' add_group 'Library', 'lib' end tool-0.2.3/spec/decoration_spec.rb0000644000175000017500000000272013122004751020373 0ustar balasankarcbalasankarcrequire 'tool/decoration' describe Tool::Decoration do shared_examples :decorate do specify "with block" do method = nil subject.decorate(-> { 42 }) { |m| method = m } expect(method).not_to be_nil expect(subject.new.send(method)).to be == 42 end specify "without block" do method = nil subject.decorate { |m| method = m } expect(method).to be_nil subject.send(:define_method, :foo) { } expect(method).to be == :foo end specify "multiple decorations" do calls = [] subject.decorate { |m| calls << :a } subject.decorate { |m| calls << :b } expect(calls).to be_empty subject.send(:define_method, :foo) { } expect(calls).to be == [:a, :b] end specify "multiple methods" do calls = [] subject.decorate { |m| calls << :a } subject.send(:define_method, :foo) { } subject.send(:define_method, :bar) { } expect(calls).to be == [:a] end end context "extending a class" do subject { Class.new.extend(Tool::Decoration) } include_examples(:decorate) end context "including in a module" do subject { Class.new.extend(Module.new { include Tool::Decoration }) } include_examples(:decorate) end context "including in a module" do subject do Class.new(Module) do def new(*) Object.new.extend(self) end include Tool::Decoration end.new end include_examples(:decorate) end end tool-0.2.3/spec/thread_local_spec.rb0000644000175000017500000000350313122004751020665 0ustar balasankarcbalasankarcrequire 'tool/thread_local' describe Tool::ThreadLocal do describe :__getobj__ do subject { Tool::ThreadLocal.new } specify 'normal access' do subject[:foo] = 'bar' expect(subject[:foo]).to be == 'bar' end specify 'concurrent access' do subject[:foo] = 'bar' value = Thread.new { subject[:foo] = 'baz' }.value expect(value).to be == 'baz' expect(subject[:foo]).to be == 'bar' end specify 'with an array as value' do list = Tool::ThreadLocal.new([]) foo = Thread.new { 10.times { list << :foo; sleep(0.01) }; list.to_a } bar = Thread.new { 10.times { list << :bar; sleep(0.01) }; list.to_a } expect(list).to be_empty list << :list expect(list) .to be == [ :list ] expect(foo.value) .to be == [ :foo ] * 10 expect(bar.value) .to be == [ :bar ] * 10 end specify 'deals with garbage collected threads' do subject[:a] = 'A' Thread.new do subject[:b] = 'B' Thread.new do subject[:c] = 'C' end.value end.value GC.start expect(subject[:a]).to be == 'A' end end describe :__size__ do subject { Tool::ThreadLocal.new } specify 'with one thread' do subject[:a] = 'A' expect(subject.__size__).to be == 1 end specify 'with multiple threads' do subject[:a] = 'A' thread = Thread.new { subject[:b] = 'B'; sleep } sleep 0.01 expect(subject.__size__).to be == 2 thread.kill end specify 'with dead threads' do next if defined? RUBY_ENGINE and RUBY_ENGINE == 'rbx' subject[:a] = 'A' Thread.new do subject[:b] = 'B' Thread.new do subject[:c] = 'C' end.value end.value GC.start expect(subject.__size__).to be == 1 end end end tool-0.2.3/Rakefile0000644000175000017500000000015013122004751015413 0ustar balasankarcbalasankarcdesc "Run specs" task(:spec) { ruby '-S rspec spec -c -w' } task(:test => :spec) task(:default => :spec)tool-0.2.3/.rspec0000644000175000017500000000004213122004751015063 0ustar balasankarcbalasankarc--color --tty -r support/coverage tool-0.2.3/lib/0000755000175000017500000000000013122004751014520 5ustar balasankarcbalasankarctool-0.2.3/lib/tool/0000755000175000017500000000000013122004751015475 5ustar balasankarcbalasankarctool-0.2.3/lib/tool/decoration.rb0000644000175000017500000000543113122004751020154 0ustar balasankarcbalasankarcrequire 'tool/thread_local' module Tool # Mixin for easy method decorations. # # @example # class Frank # extend Tool::Decoration # def self.get(path, &block) # decorate(block) do |method| # puts "mapping GET #{path} to #{method}" # end # end # end # # # Output: # # mapping GET /hi to __generated1 # # mapping GET / to index # # mapping GET /index.php to index # class MyApp < Frank # get '/hi' do # "Hello World" # end # # get '/'; get '/index.php' # def index # "This is the index page." # end # end module Decoration module Initializer # Make sure decorations list is initializsed upon instantiation. # @!visibility private def initialize(*) setup_decorations super end end module Setup # Make sure decorations list is initializsed if Decoration is included. # @!visibility private def included(object) case object when Class then object.send(:include, Initializer) when Module then object.extend(Setup) end super end # Make sure decorations list is initializsed if Decoration extends an object. # @!visibility private def extended(object) object.send(:setup_decorations) super end end extend Setup # Set up a decoration. # # @param [Proc, UnboundMethod, nil] block used for defining a method right away # @param [String, Symbol] name given to the generated method if block is given # @yield callback called with method name once method is defined # @yieldparam [Symbol] method name of the method that is to be decorated def decorate(block = nil, name: "generated", &callback) @decorations << callback if block alias_name = "__" << name.to_s.downcase.gsub(/[^a-z]+/, ?_) << ?1 alias_name = alias_name.succ while respond_to? alias_name, true without_decorations { define_method(name, &block) } alias_method(alias_name, name) remove_method(name) private(alias_name) end end # Runs a given block without applying decorations defined outside of the block. # Decorations defined before the block will still be registered after the block. # # @yield block to run without decorations def without_decorations @decorations.clear if was = @decorations.to_a.dup yield ensure @decorations.replace(was) if was end def method_added(name) @decorations.each { |d| d.call(name) }.clear super end def setup_decorations @decorations = Tool::ThreadLocal.new([]) end private :method_added, :setup_decorations private_constant :Initializer, :Setup end end tool-0.2.3/lib/tool/thread_local.rb0000644000175000017500000000413713122004751020450 0ustar balasankarcbalasankarcrequire 'delegate' require 'weakref' require 'thread' module Tool # Have thread local values without them actually being thread global. # # Advantages: # * Values for all threads are garbage collected when ThreadLocal instance is. # * Values for specific thread are garbage collected when thread is. # * No hidden global state. # * Supports other data types besides hashes. # # @example To replace Thread.current hash access # local = Tool::ThreadLocal.new # local[:key] = "value" # # Thread.new do # local[:key] = "other value" # puts local[:key] # other value # end.join # # puts local[:key] # value # # @example Usage with Array # local = Tool::ThreadLocal.new([:foo]) # local << :bar # # Thread.new { p local }.join # [:foo] # p local # [:foo, :bar] class ThreadLocal < Delegator @mutex ||= Mutex.new @locals ||= [] # Thread finalizer. # @!visibility private def self.cleanup(id) @locals.keep_if do |local| next false unless local.weakref_alive? local.__cleanup__ true end end # Generates weak reference to thread and sets up finalizer. # @return [WeakRef] # @!visibility private def self.ref(thread) thread[:weakref] ||= begin ObjectSpace.define_finalizer(thread, method(:cleanup)) WeakRef.new(thread) end end # @see #initialize # @!visibility private def self.new(*) result = super @mutex.synchronize { @locals << WeakRef.new(result) } result end def initialize(default = {}) @default = default.dup @map = {} end # @see Delegator # @!visibility private def __getobj__ ref = ::Tool::ThreadLocal.ref(Thread.current) @map[ref] ||= @default.dup end # @return [Integer] number of threads with specific locals # @!visibility private def __size__ @map.size end # Remove locals for dead or GC'ed threads # @!visibility private def __cleanup__ @map.keep_if { |key, value| key.weakref_alive? and key.alive? } end end endtool-0.2.3/lib/tool/warning_filter.rb0000644000175000017500000000071713122004751021041 0ustar balasankarcbalasankarcrequire 'delegate' module Tool # Enables Ruby's built-in warnings (-w) but filters out those caused by third-party gems. # Does not invlove any manual set up. # # @example # require 'tool/warning_filter' # Foo = 10 # Foo = 20 class WarningFilter < DelegateClass(IO) $stderr = new($stderr) $VERBOSE = true # @!visibility private def write(line) super if line !~ /^\S+gems\/ruby\-\S+:\d+: warning:/ end end end tool-0.2.3/lib/tool/equality_map.rb0000644000175000017500000000343113122004751020515 0ustar balasankarcbalasankarcmodule Tool # A simple wrapper around ObjectSpace::WeakMap that allows matching keys by equality rather than identity. # Used for caching. Note that `fetch` is not guaranteed to return the object, even if it has not been # garbage collected yet, especially when used concurrently. Therefore, the block passed to `fetch` has to # be idempotent. # # @example # class ExpensiveComputation # @map = Tool::EqualityMap.new # # def self.new(*args) # @map.fetch(*args) { super } # end # end # # @see #fetch class EqualityMap attr_reader :map def self.new defined?(ObjectSpace::WeakMap) ? super : {} end def initialize @keys = {} @map = ObjectSpace::WeakMap.new end # @param [Array<#hash>] key for caching # @yield block that will be called to populate entry if missing (has to be idempotent) # @return value stored in map or result of block def fetch(*key) identity = @keys[key.hash] key = identity == key ? identity : key # it is ok that this is not thread-safe, worst case it has double cost in # generating, object equality is not guaranteed anyways @map[key] ||= track(key, yield) end # @param [#hash] key for identifying the object # @param [Object] object to be stored # @return [Object] same as the second parameter def track(key, object) ObjectSpace.define_finalizer(object, finalizer(key.hash)) @keys[key.hash] = key object end # Finalizer proc needs to be generated in different scope so it doesn't keep a reference to the object. # # @param [Fixnum] hash for key # @return [Proc] finalizer callback def finalizer(hash) proc { @keys.delete(hash) } end private :track, :finalizer end endtool-0.2.3/lib/tool/version.rb0000644000175000017500000000004313122004751017504 0ustar balasankarcbalasankarcmodule Tool VERSION = '0.2.3' endtool-0.2.3/LICENSE0000644000175000017500000000205113122004751014755 0ustar balasankarcbalasankarcCopyright (c) 2013, 2014 Konstantin Haase Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.tool-0.2.3/.travis.yml0000644000175000017500000000010013122004751016052 0ustar balasankarcbalasankarclanguage: ruby rvm: - 2.0.0 - 2.1.0 - 2.1.1 - rbx-2.3.0 tool-0.2.3/examples/0000755000175000017500000000000013122004751015570 5ustar balasankarcbalasankarctool-0.2.3/examples/frank.rb0000644000175000017500000000051213122004751017214 0ustar balasankarcbalasankarcrequire 'tool/decoration' class Frank extend Tool::Decoration def self.get(path, &block) decorate(block) do |method| puts "mapping GET #{path} to #{method}" end end end class MyApp < Frank get '/hi' do "Hello World" end get '/'; get '/index.php' def index "This is the index page." end end