ruby-interface 0.0.6

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.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
@@ -0,0 +1,42 @@
1
+ # rcov generated
2
+ coverage
3
+
4
+ # rdoc generated
5
+ rdoc
6
+
7
+ # yard generated
8
+ doc
9
+ .yardoc
10
+
11
+ # bundler
12
+ .bundle
13
+
14
+ # jeweler generated
15
+ pkg
16
+
17
+ # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
18
+ #
19
+ # * Create a file at ~/.gitignore
20
+ # * Include files you want ignored
21
+ # * Run: git config --global core.excludesfile ~/.gitignore
22
+ #
23
+ # After doing this, these files will be ignored in all your git projects,
24
+ # saving you from having to 'pollute' every project you touch with them
25
+ #
26
+ # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
27
+ #
28
+ # For MacOS:
29
+ #
30
+ #.DS_Store
31
+ #
32
+ # For TextMate
33
+ #*.tmproj
34
+ #tmtags
35
+ #
36
+ # For emacs:
37
+ #*~
38
+ #\#*
39
+ #.\#*
40
+ #
41
+ # For vim:
42
+ #*.swp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "rspec", "~> 2.3.0"
5
+ gem "yard", "~> 0.6.0"
6
+ gem "bundler", "~> 1.0.0"
7
+ gem "jeweler", "~> 1.5.2"
8
+ gem "rcov", ">= 0"
9
+ gem "bluecloth"
10
+ gem "rr"
11
+ end
12
+
13
+ gemspec
@@ -0,0 +1,44 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ruby-interface (0.0.6)
5
+ activesupport (> 0.1)
6
+ i18n (> 0.1)
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ activesupport (3.0.7)
12
+ bluecloth (2.1.0)
13
+ diff-lcs (1.1.2)
14
+ git (1.2.5)
15
+ i18n (0.5.0)
16
+ jeweler (1.5.2)
17
+ bundler (~> 1.0.0)
18
+ git (>= 1.2.5)
19
+ rake
20
+ rake (0.8.7)
21
+ rcov (0.9.9)
22
+ rr (1.0.2)
23
+ rspec (2.3.0)
24
+ rspec-core (~> 2.3.0)
25
+ rspec-expectations (~> 2.3.0)
26
+ rspec-mocks (~> 2.3.0)
27
+ rspec-core (2.3.1)
28
+ rspec-expectations (2.3.0)
29
+ diff-lcs (~> 1.1.2)
30
+ rspec-mocks (2.3.0)
31
+ yard (0.6.5)
32
+
33
+ PLATFORMS
34
+ ruby
35
+
36
+ DEPENDENCIES
37
+ bluecloth
38
+ bundler (~> 1.0.0)
39
+ jeweler (~> 1.5.2)
40
+ rcov
41
+ rr
42
+ rspec (~> 2.3.0)
43
+ ruby-interface!
44
+ yard (~> 0.6.0)
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Andrew Rudenko
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,93 @@
1
+ RubyInterface
2
+ =============
3
+
4
+ Простенький патерн определения интерфейсов в руби. В противовес стандартным миксинам, для каждого интерфейса создается свой класс и соответсвенно экземпляр класса для каждого объекта с интерфейсом.
5
+
6
+ module Tree
7
+ extend RubyInterface
8
+ interface :tree do
9
+ include Enumerable
10
+ attr_accessor :parent
11
+
12
+ def childs
13
+ @childs ||= []
14
+ end
15
+
16
+ def each(&blk)
17
+ blk.call(owner)
18
+ childs.each { |v| v.tree.each(&blk) }
19
+ end
20
+
21
+ def set_parent(parent)
22
+ parent.tree.childs << owner
23
+ @parent = parent
24
+ end
25
+ end
26
+ end
27
+
28
+ class A
29
+ include Tree
30
+ end
31
+
32
+ При разработке интерфейса не нужно задумываться о конфликтах имен переменных, методов, можно делать все что угодно. Аргументом к методу interface передается название метода, по которому этот интерфейс будет доступен.
33
+
34
+ a = A.new
35
+ b = A.new
36
+
37
+ a.tree.set_parent b
38
+ b.tree.childs # => [a]
39
+ b.tree.map { |o| o } # => [b, a]
40
+
41
+ А при использовании методов относящихся к интерфейсу мы явно видим к какому же интерфейсу он относится. Всем профит!
42
+
43
+ В интерфейсе доступен метод owner, возвращающий родительский объект. У класса интерфейса есть <tt>interface_base</tt>, возвращающий класс, куда интерфейс был заинклужен.
44
+
45
+ Помимо инстанс метода, создается так же класс-метод. В него можно передать блок, который выполнится в скоупе класса интерфейса. Сам метод возвращает класс интерфейса.
46
+
47
+ module StateMachine
48
+ extend RubyInterface
49
+ interface :state_machine do
50
+ def self.state(name)
51
+ puts "New state #{name}"
52
+ end
53
+ end
54
+ end
55
+
56
+ class A
57
+ include StateMachine
58
+
59
+ state_machine do
60
+ state(:parked) # => New state parked
61
+ state(:idling) # => New state idling
62
+ end
63
+ end
64
+
65
+ При наследовании класса с интерфейсом, создается новый класс интерфейса и наследуется от предыдущего, т.е. повторяет иерархию класса, в который он включен.
66
+
67
+ Если в блоке <tt>interface</tt> вызывается метод <tt>interfaced</tt>, то исполнение блока, передаваемого <tt>interfaced</tt>
68
+ происходит после добавления интерфейса в класс, в контексте этого класса.
69
+
70
+ Пример:
71
+
72
+ module A
73
+ extend RubyInterface
74
+ interface :int do
75
+ interfaced do
76
+ def baz
77
+ self.class.int_interface.foo
78
+ end
79
+ end
80
+
81
+ def self.foo
82
+ "bar"
83
+ end
84
+ end
85
+ end
86
+
87
+ class B
88
+ include A
89
+ end
90
+
91
+ B.new.baz # => "bar"
92
+
93
+ В каждом модуле может быть определено произвольное количество интерфейсов
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ Bundler::GemHelper.install_tasks
15
+
16
+ require 'rspec/core'
17
+ require 'rspec/core/rake_task'
18
+ RSpec::Core::RakeTask.new(:spec) do |spec|
19
+ spec.pattern = FileList['spec/**/*_spec.rb']
20
+ end
21
+
22
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
23
+ spec.pattern = 'spec/**/*_spec.rb'
24
+ spec.rcov = true
25
+ end
26
+
27
+ task :default => :spec
28
+
29
+ require 'yard'
30
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,3 @@
1
+ module RubyInterface
2
+ VERSION = "0.0.6"
3
+ end
@@ -0,0 +1,8 @@
1
+ class RubyInterfaceHandler < YARD::Handlers::Ruby::Base
2
+ handles method_call(:interface)
3
+
4
+ def process
5
+ parse_block(statement.last.last)
6
+ rescue YARD::Handlers::NamespaceMissingError
7
+ end
8
+ end
@@ -0,0 +1,71 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'active_support/core_ext/class/attribute'
3
+ require 'active_support/core_ext/string/inflections'
4
+
5
+ module RubyInterface
6
+ def interface(method_name, &interface_body)
7
+ mod_inst = self.const_set("#{method_name.to_s.camelize}InstanceMethods", Module.new)
8
+ mod_inst.module_eval <<-EOT, __FILE__, __LINE__ + 1
9
+ def #{method_name}
10
+ @#{method_name}_interface ||= self.class.#{method_name}_interface.new(self)
11
+ end
12
+ EOT
13
+
14
+
15
+ mod_class = self.const_set("#{method_name.to_s.camelize}ClassMethods", Module.new)
16
+ mod_class.module_eval <<-EOT, __FILE__, __LINE__ + 1
17
+ def #{method_name}(&blk)
18
+ self.#{method_name}_interface.class_eval(&blk) if blk
19
+ self.#{method_name}_interface
20
+ end
21
+
22
+ def inherited(subclass)
23
+ new_class = subclass.const_set("#{method_name.to_s.camelize}InterfaceClass", Class.new(self.#{method_name}_interface))
24
+ new_class.interface_base = subclass
25
+ subclass.#{method_name}_interface = new_class
26
+ super
27
+ end
28
+ EOT
29
+
30
+ interface_module = self
31
+
32
+ add_interface do |base|
33
+ base.send(:class_attribute, "#{method_name}_interface")
34
+ interface_class = base.const_set("#{method_name.to_s.camelize}InterfaceClass", Class.new(RubyInterface::InterfaceClass))
35
+ interface_class.interface_base = base
36
+ interface_class.class_eval(&interface_body) if interface_body
37
+ base.send("#{method_name}_interface=", interface_class)
38
+ base.extend mod_class
39
+ base.send :include, mod_inst
40
+ base.class_eval(&interface_class.interfaced) if interface_class.interfaced
41
+ end
42
+
43
+ interface_module.define_singleton_method(:included) do |base|
44
+ @_deps.each {|d| d.call(base)}
45
+ end
46
+ end
47
+
48
+ private
49
+ def add_interface &block
50
+ @_deps ||= []
51
+ @_deps << block
52
+ end
53
+
54
+ class InterfaceClass
55
+ class_attribute :interface_base
56
+ attr_accessor :owner
57
+ def initialize(owner)
58
+ @owner = owner
59
+ end
60
+
61
+ class << self
62
+ def interfaced(&block)
63
+ if block_given?
64
+ @_interfaced_block = block
65
+ else
66
+ @_interfaced_block
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "ruby-interface/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{ruby-interface}
7
+ s.version = RubyInterface::VERSION
8
+ s.summary = "Ruby interface"
9
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ s.authors = ["Andrew Rudenko", "Nick Recobra"]
11
+ s.date = %q{2011-03-24}
12
+ s.description = %q{Ruby interface}
13
+ s.email = %q{ceo@prepor.ru}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency('activesupport', ["> 0.1"])
21
+ s.add_dependency(%q<i18n>, ["> 0.1"])
22
+ end
@@ -0,0 +1,127 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe RubyInterface do
5
+ def make_interface
6
+ mod = Module.new
7
+ mod.extend(RubyInterface)
8
+ mod
9
+ end
10
+
11
+ describe "Создание интерфейса" do
12
+ let(:mod) { make_interface.tap { |v| v.interface(:test) } }
13
+
14
+ it "должен вызываться interfaced, если определен" do
15
+ @proc = proc {}
16
+ klass = Class.new
17
+ mod.interface :int do
18
+ interfaced(&@proc)
19
+ end
20
+ mock(klass).class_eval(&@proc)
21
+ mock(klass).class_eval(&@proc)
22
+ klass.send :include, mod
23
+ end
24
+
25
+ describe "Класс с интерфейсом" do
26
+ subject { klass }
27
+ let(:klass) { Class.new.tap { |v| v.send :include, mod } }
28
+ let(:interface_klass) { klass.test_interface }
29
+ its(:test) { should eq(interface_klass) }
30
+
31
+ it "может дополнять класс интерфейса" do
32
+ interface_klass.respond_to?(:test).should be_false
33
+ klass.test do
34
+ mattr_accessor :test
35
+ end
36
+ interface_klass.respond_to?(:test).should be_true
37
+ end
38
+
39
+ it "должен содержать класс интерфейса" do
40
+ klass::TestInterfaceClass.ancestors.should include(RubyInterface::InterfaceClass)
41
+ klass.test_interface.should eq(klass::TestInterfaceClass)
42
+ end
43
+
44
+ describe "Класс интерфейса" do
45
+ subject { interface_klass }
46
+ its(:interface_base) { should eq(klass) }
47
+
48
+ describe "Объект с интерфейсом" do
49
+ subject { obj }
50
+ let(:obj) { klass.new }
51
+ its(:test) { should be_kind_of(interface_klass)}
52
+ describe "Объект интерфейса" do
53
+ subject { interface_obj }
54
+ let(:interface_obj) { obj.test }
55
+ its(:owner) { should eq(obj) }
56
+ let(:mod) do
57
+ make_interface.tap do |v|
58
+ v.interface(:test) do
59
+ def foo
60
+ "bar"
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ it "должен поддерживать методы определенные в интерфейсе" do
67
+ interface_obj.foo.should eq('bar')
68
+ end
69
+ end
70
+ end
71
+
72
+ describe "при наследовании" do
73
+ let(:klass2) { Class.new(klass) }
74
+ it "должен создавать новый интерфейс-класс, наследуя старый" do
75
+ klass2.test.ancestors.should include(interface_klass)
76
+ end
77
+ it "новый интерфейс-класс должен ссылаться на новый класс" do
78
+ klass2.test.interface_base.should eq(klass2)
79
+ end
80
+ end
81
+ end
82
+
83
+
84
+ end
85
+ end
86
+
87
+ describe "Модуль с двумя интерфейсами" do
88
+ before(:each) do
89
+ @mod = Module.new
90
+ @mod.send :extend, RubyInterface
91
+ @mod.interface :first do
92
+ interfaced do
93
+ def baz
94
+ first.foo
95
+ end
96
+ end
97
+
98
+ def foo
99
+ "bar"
100
+ end
101
+ end
102
+
103
+ @mod.interface :second do
104
+ interfaced do
105
+ self.second_interface.foo
106
+ end
107
+
108
+ def self.foo
109
+ "baz"
110
+ end
111
+ end
112
+ end
113
+
114
+ it "не должно происходить ошибок при подключении модуля с двумя интерфейсами" do
115
+ b = Class.new
116
+ lambda { b.send :include, @mod }.should_not raise_error
117
+ end
118
+
119
+ it "должны появиться интерфейсы с методами" do
120
+ b = Class.new
121
+ b.send :include, @mod
122
+ bb = b.new
123
+ bb.baz.should eq("bar")
124
+ bb.second_interface.foo.should eq("baz")
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'rr'
5
+ require 'ruby_interface'
6
+
7
+ # Requires supporting files with custom matchers and macros, etc,
8
+ # in ./support/ and its subdirectories.
9
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
10
+
11
+ RSpec.configure do |config|
12
+ config.mock_framework = :rr
13
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-interface
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 6
9
+ version: 0.0.6
10
+ platform: ruby
11
+ authors:
12
+ - Andrew Rudenko
13
+ - Nick Recobra
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-24 00:00:00 +03:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activesupport
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">"
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 1
31
+ version: "0.1"
32
+ type: :runtime
33
+ prerelease: false
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: i18n
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">"
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 0
44
+ - 1
45
+ version: "0.1"
46
+ type: :runtime
47
+ prerelease: false
48
+ version_requirements: *id002
49
+ description: Ruby interface
50
+ email: ceo@prepor.ru
51
+ executables: []
52
+
53
+ extensions: []
54
+
55
+ extra_rdoc_files: []
56
+
57
+ files:
58
+ - .document
59
+ - .gitignore
60
+ - .rspec
61
+ - Gemfile
62
+ - Gemfile.lock
63
+ - LICENSE.txt
64
+ - README.md
65
+ - Rakefile
66
+ - lib/ruby-interface/version.rb
67
+ - lib/ruby-interface/yard.rb
68
+ - lib/ruby_interface.rb
69
+ - ruby-interface.gemspec
70
+ - spec/ruby_interface_spec.rb
71
+ - spec/spec_helper.rb
72
+ has_rdoc: true
73
+ homepage:
74
+ licenses: []
75
+
76
+ post_install_message:
77
+ rdoc_options: []
78
+
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ hash: 1341302791609239659
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ requirements: []
99
+
100
+ rubyforge_project:
101
+ rubygems_version: 1.3.7
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: Ruby interface
105
+ test_files:
106
+ - spec/ruby_interface_spec.rb
107
+ - spec/spec_helper.rb