interfaced 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.
- checksums.yaml +7 -0
- data/lib/interfaced.rb +104 -0
- data/spec/interfaced_spec.rb +126 -0
- metadata +50 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 21dbf466abb75317aba888e47ffc1d62ead219d6154719b95f521581c9762b69
|
|
4
|
+
data.tar.gz: daf48b61f6b8d27c4795539657b9e2a6daab467ab86e7c79939e69395fdee3e4
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 7bf520ca3db883672c8c3e25105b9d97f8c8f9d64b2139a796ac03a7ac52547d1424656207ec470795593bce726d0738d41a2917258449fe103417601896c811
|
|
7
|
+
data.tar.gz: 91acf860185fd0b20edfa05539ca8a3bb81f9d311d333e7045bbfe86b3e6a1c1b2c9305ef0c30e635a50fab62b33fcc4ff8cf992930fefe05dfc9528fadd2b7e
|
data/lib/interfaced.rb
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "interfaced/version"
|
|
4
|
+
|
|
5
|
+
# A module for implementing Java-like interfaces in Ruby.
|
|
6
|
+
# More information about Java interfaces:
|
|
7
|
+
# http://java.sun.com/docs/books/tutorial/java/concepts/interface.html
|
|
8
|
+
|
|
9
|
+
module Interfaced
|
|
10
|
+
class MethodMissing < StandardError; end
|
|
11
|
+
|
|
12
|
+
private def extend_object(obj) # based on https://www.rubydoc.info/stdlib/core/1.8.7/Module#extend_object-instance_method callback
|
|
13
|
+
return append_features(obj) if obj.is_a?(Interfaced)
|
|
14
|
+
|
|
15
|
+
append_features(class << obj; self end) # source code: https://github.com/ruby/ruby/blob/9c236f114001fb557fde97632136c84e669e489c/eval.c#L1029
|
|
16
|
+
included(obj)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private def append_features(mod) # based on https://www.rubydoc.info/stdlib/core/1.8.7/Module:append_features
|
|
20
|
+
return super if mod.is_a?(Interfaced)
|
|
21
|
+
|
|
22
|
+
# Taking required methods from sub interfaces
|
|
23
|
+
sub_interfaces = (ancestors - [self]).select { |x| x.is_a?(Interfaced) }
|
|
24
|
+
inherited_req_methods = sub_interfaces.map { |x| x.instance_variable_get('@req_methods') }
|
|
25
|
+
|
|
26
|
+
# Store required methods (both from current interface and sub interfaces)
|
|
27
|
+
req_methods = @req_methods + inherited_req_methods.flatten
|
|
28
|
+
@unreq_methods ||= []
|
|
29
|
+
|
|
30
|
+
# Iterate over the list of required methods, and raise
|
|
31
|
+
# an error if the method has not been defined.
|
|
32
|
+
(req_methods - @unreq_methods).uniq.each do |req_method|
|
|
33
|
+
unless mod.instance_methods(true).include?(req_method)
|
|
34
|
+
raise Interfaced::MethodMissing, req_method
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
super mod
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Accepts an array of method names that define the interface. When this
|
|
42
|
+
# module is included/implemented, those method names must have already been
|
|
43
|
+
# defined or error Interfaced::MethodMissing will be raised.
|
|
44
|
+
def required_methods(*req_methods)
|
|
45
|
+
@req_methods = req_methods
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Accepts an array of method names that are to be removed from required list.
|
|
49
|
+
# Presumably you would use this in a sub-interfaces where
|
|
50
|
+
# you only wanted a partial implementation of an existing interface.
|
|
51
|
+
def unrequired_methods(*unreq_methods)
|
|
52
|
+
@unreq_methods ||= []
|
|
53
|
+
@unreq_methods += unreq_methods
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
alias :extends :extend
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class Object
|
|
60
|
+
# The interfaced method (or its alias `interface`) created an interface module
|
|
61
|
+
# that sets a list of methods that must be included or implemented
|
|
62
|
+
# If the methods are not defined, an Interfaced::MethodMissing error is raised.
|
|
63
|
+
# An Interface can extend any other existing interfaces as well.
|
|
64
|
+
# The similar implementation to TypeScript
|
|
65
|
+
# (https://www.typescripttutorial.net/typescript-tutorial/typescript-extend-interface/)
|
|
66
|
+
#
|
|
67
|
+
# Examples:
|
|
68
|
+
#
|
|
69
|
+
# # require :foo and :bar methods
|
|
70
|
+
# FooBarInterface = interfaced_with { # interface, interfaced, create_interface are alies
|
|
71
|
+
# required_methods :foo, :bar
|
|
72
|
+
# }
|
|
73
|
+
#
|
|
74
|
+
# # A sub-interface that extends FooBarInterface but make changes to it
|
|
75
|
+
# FooBazInterface = interfaced_with {
|
|
76
|
+
# extends FooBarInterface # extend is an alias
|
|
77
|
+
# required_methods :baz
|
|
78
|
+
# unrequired_methods :bar
|
|
79
|
+
# }
|
|
80
|
+
#
|
|
81
|
+
# # Raises an Interfaced::MethodMissing error because :bar is not defined.
|
|
82
|
+
# class MyClass
|
|
83
|
+
# def foo = ...
|
|
84
|
+
# def baz = ...
|
|
85
|
+
#
|
|
86
|
+
# implements FooBazInterface # use_interface is an alias
|
|
87
|
+
# end
|
|
88
|
+
#
|
|
89
|
+
def interfaced_with(&block)
|
|
90
|
+
Module.new do |mod|
|
|
91
|
+
mod.extend(Interfaced)
|
|
92
|
+
mod.instance_eval(&block)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
alias :interface :interfaced_with
|
|
97
|
+
alias :interfaced :interfaced_with
|
|
98
|
+
alias :create_interface :interfaced_with
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class Module
|
|
102
|
+
alias :implements :include
|
|
103
|
+
alias :use_interface :include
|
|
104
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
require "rspec"
|
|
2
|
+
require "interfaced"
|
|
3
|
+
|
|
4
|
+
RSpec.describe Interfaced do
|
|
5
|
+
foo_bar_interface = interfaced_with do
|
|
6
|
+
required_methods :foo, :bar
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
foo_baz_interface = interfaced_with do
|
|
10
|
+
extends foo_bar_interface
|
|
11
|
+
required_methods :baz
|
|
12
|
+
unrequired_methods :bar
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
let(:empty_class) { Class.new }
|
|
16
|
+
|
|
17
|
+
let(:foo_bar_class) do
|
|
18
|
+
Class.new do
|
|
19
|
+
def foo; end
|
|
20
|
+
|
|
21
|
+
def bar; end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
let(:foo_baz_class) do
|
|
26
|
+
Class.new do
|
|
27
|
+
def foo; end
|
|
28
|
+
|
|
29
|
+
def baz; end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe "version" do
|
|
34
|
+
subject { Interfaced::VERSION }
|
|
35
|
+
|
|
36
|
+
it { is_expected.to eq("0.1.0") }
|
|
37
|
+
it { is_expected.to be_frozen }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe "#interfaced_with" do
|
|
41
|
+
context "with single interface" do
|
|
42
|
+
context "foo_bar_interface interface with :foo and :bar required method" do
|
|
43
|
+
context "casting on empty class (without required method being implemented)" do
|
|
44
|
+
it "raises an error" do
|
|
45
|
+
expect { empty_class.new.extend(foo_bar_interface) }.to raise_error(Interfaced::MethodMissing)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context "casting on class with :foo and :bar methods being implemented" do
|
|
50
|
+
it "doesn't raise an error" do
|
|
51
|
+
expect { foo_bar_class.new.extend(foo_bar_interface) }.not_to raise_error
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
context "casting on class with :foo and :baz methods being implemented" do
|
|
56
|
+
it "raises an error" do
|
|
57
|
+
expect { foo_baz_class.new.extend(foo_bar_interface) }.to raise_error(Interfaced::MethodMissing)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context "foo_baz_interface with :foo and :baz required methods (:foo and :bar from sub interface, :bar unrequired)" do
|
|
63
|
+
context "casting on empty class (without required method being implemented)" do
|
|
64
|
+
it "raises an error" do
|
|
65
|
+
expect { empty_class.new.extend(foo_baz_interface) }.to raise_error(Interfaced::MethodMissing)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context "casting on class with :foo and :bar methods being implemented" do
|
|
70
|
+
it "raises an error" do
|
|
71
|
+
expect { foo_bar_class.new.extend(foo_baz_interface) }.to raise_error(Interfaced::MethodMissing)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
context "casting on class with :foo and :baz methods being implemented" do
|
|
76
|
+
it "doesn't raise an error" do
|
|
77
|
+
expect { foo_baz_class.new.extend(foo_baz_interface) }.not_to raise_error
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context "with multiple interfaces" do
|
|
84
|
+
context "casting on empty class (without required method being implemented" do
|
|
85
|
+
it "raises an error" do
|
|
86
|
+
expect do
|
|
87
|
+
empty_class.new.extend(foo_bar_interface)
|
|
88
|
+
empty_class.new.extend(foo_baz_interface)
|
|
89
|
+
end.to raise_error(Interfaced::MethodMissing)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
context "casting on class with :foo and :bar methods being implemented" do
|
|
94
|
+
it "raises and error" do
|
|
95
|
+
expect do
|
|
96
|
+
foo_bar_class.new.extend(foo_bar_interface)
|
|
97
|
+
foo_bar_class.new.extend(foo_baz_interface)
|
|
98
|
+
end.to raise_error(Interfaced::MethodMissing)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
context "casting on class with :foo and :baz methods being implemented" do
|
|
103
|
+
context "when one of interface with required method that class doesn't implement" do
|
|
104
|
+
it "raises an error" do
|
|
105
|
+
expect do
|
|
106
|
+
foo_baz_class.new.extend(foo_bar_interface)
|
|
107
|
+
foo_baz_class.new.extend(foo_baz_interface)
|
|
108
|
+
end.to raise_error(Interfaced::MethodMissing)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
context "when both interfaces with required methods that class implements" do
|
|
113
|
+
foo_interface = interface { required_methods :foo }
|
|
114
|
+
baz_interface = interface { required_methods :baz }
|
|
115
|
+
|
|
116
|
+
it "doesn't raise an error" do
|
|
117
|
+
expect do
|
|
118
|
+
foo_baz_class.new.extend(foo_interface)
|
|
119
|
+
foo_baz_class.new.extend(baz_interface)
|
|
120
|
+
end.not_to raise_error
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: interfaced
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Oleh Klym
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2024-01-24 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: |2
|
|
14
|
+
Module that providers Java-like interfaces for Ruby.
|
|
15
|
+
It lets you define a set a methods that must be defined in the
|
|
16
|
+
including class or module without the need of inheriting it from interface class.
|
|
17
|
+
Otherwise an error is raised.
|
|
18
|
+
email: k4138314@gmail.com
|
|
19
|
+
executables: []
|
|
20
|
+
extensions: []
|
|
21
|
+
extra_rdoc_files: []
|
|
22
|
+
files:
|
|
23
|
+
- lib/interfaced.rb
|
|
24
|
+
- spec/interfaced_spec.rb
|
|
25
|
+
homepage: https://github.com/PET-PROJECTING/interfaced
|
|
26
|
+
licenses:
|
|
27
|
+
- MIT
|
|
28
|
+
metadata:
|
|
29
|
+
source_code_uri: https://github.com/PET-PROJECTING/interfaced
|
|
30
|
+
post_install_message:
|
|
31
|
+
rdoc_options: []
|
|
32
|
+
require_paths:
|
|
33
|
+
- lib
|
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0'
|
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '0'
|
|
44
|
+
requirements: []
|
|
45
|
+
rubygems_version: 3.4.10
|
|
46
|
+
signing_key:
|
|
47
|
+
specification_version: 4
|
|
48
|
+
summary: Module that providers Java-like interface to Ruby
|
|
49
|
+
test_files:
|
|
50
|
+
- spec/interfaced_spec.rb
|