feature_envy 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/README.md +27 -0
- data/lib/feature_envy/final_class.rb +92 -0
- data/lib/feature_envy/internal.rb +23 -0
- data/lib/feature_envy/version.rb +6 -0
- data/lib/feature_envy.rb +16 -0
- data/test/final_class_test.rb +43 -0
- data/test/internal_test.rb +25 -0
- data/test/support/assertions.rb +11 -0
- data/test/test_helper.rb +12 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ebacd4b2d2a8d5292de874248a212a72b02a7bc13b5279b08f020a88c8973e71
|
4
|
+
data.tar.gz: bca18e8eab4ef3d67efa38b65a09f4ede7291873a95a266748bbfd79e2b9ab45
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 534c102e65795b30fd4cdd42ef10b34b062776642f145ebddffd3865fc39e81657c870e645e782362f39c45b1d6afb2e565caefb2932da69fcd84d71121a5548
|
7
|
+
data.tar.gz: 0be465e644e71f4b51f14acb6e6e496c43618e5fbe8e13cc2a288291789074bcbc5d297589b4cb4b3d36eff742eb5a5351b3db81298a7637e22f348e78afb53b
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Feature Envy
|
2
|
+
|
3
|
+
Feature Envy enhances Ruby with features found in other programming languages.
|
4
|
+
|
5
|
+
**WARNING**: This gem is still in development and a stable release hasn't been
|
6
|
+
made yet. Bug reports and contributions are welcome!
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
You can install the gem by running `gem install feature_envy` or adding it to
|
11
|
+
`Gemfile`:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem "feature_envy"
|
15
|
+
```
|
16
|
+
|
17
|
+
Don't forget to run `bundle install` afterwards.
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Features are independent from each other and can be enabled one-by-one when
|
22
|
+
needed. Please refer to individual feature documentation to understand how to
|
23
|
+
enabled them.
|
24
|
+
|
25
|
+
## Author
|
26
|
+
|
27
|
+
This gem is developed and maintained by [Greg Navis](http://www.gregnavis.com).
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FeatureEnvy
|
4
|
+
# Final classes.
|
5
|
+
#
|
6
|
+
# A final class is a class that cannot be inherited from. In other words, a
|
7
|
+
# final class enforces the invariant that it has no subclasses. Existence of
|
8
|
+
# subclasses if checked **at the moment a class is defined final** (to catch
|
9
|
+
# cases where a class is reopened and made final after subclasses were
|
10
|
+
# defined) and when.
|
11
|
+
#
|
12
|
+
# The module can be used in two ways:
|
13
|
+
#
|
14
|
+
# 1. Using it as a refinement and calling +.final!+ in bodies of classes that
|
15
|
+
# should be marked final.
|
16
|
+
# 2. Extending the class with {FeatureEnvy::FinalClass}.
|
17
|
+
#
|
18
|
+
# See the example below for details.
|
19
|
+
#
|
20
|
+
# @example Final classes with refinements
|
21
|
+
# module Models
|
22
|
+
# # Use the refinement within the module, so that all classes defined
|
23
|
+
# # within support the final! method.
|
24
|
+
# using FeatureEnvy::FinalClass
|
25
|
+
#
|
26
|
+
# class User < Base
|
27
|
+
# # Mark the User class final.
|
28
|
+
# final!
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# @example Final classes without refinements
|
33
|
+
# module Models
|
34
|
+
# class User < Base
|
35
|
+
# # Mark the User class final.
|
36
|
+
# extend FeatureEnvy::FinalClass
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
module FinalClass
|
40
|
+
# An error representing a final class invariant violation.
|
41
|
+
class Error < FeatureEnvy::Error
|
42
|
+
def initialize final_class:, subclasses:
|
43
|
+
super(<<~ERROR)
|
44
|
+
Class #{FeatureEnvy::Internal.class_name final_class} is final but the following subclasses were defined:
|
45
|
+
|
46
|
+
#{subclasses.map { "- #{FeatureEnvy::Internal.class_name _1}" }.join("\n")}
|
47
|
+
ERROR
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
refine Class do
|
52
|
+
def final!
|
53
|
+
extend FinalClass
|
54
|
+
end
|
55
|
+
|
56
|
+
def final?
|
57
|
+
FinalClass.final? self
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# The array of classes that were marked as final.
|
62
|
+
@classes = []
|
63
|
+
|
64
|
+
class << self
|
65
|
+
def extended final_class
|
66
|
+
# The class must be marked final first, before we check whether there
|
67
|
+
# are already existing subclasses. If the error were raised first then
|
68
|
+
# .final? would return +false+ causing confusion: if the class isn't
|
69
|
+
# final then why was the error raised?
|
70
|
+
@classes << final_class
|
71
|
+
|
72
|
+
subclasses = Internal.subclasses final_class
|
73
|
+
return if subclasses.empty?
|
74
|
+
|
75
|
+
raise Error.new(final_class: final_class, subclasses: subclasses)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Determines whether a given class is marked final.
|
79
|
+
#
|
80
|
+
# @param klass [Class] The class whose finality should be checked.
|
81
|
+
# @return [Boolean] +true+ if +klass+ is final, +false+ otherwise.
|
82
|
+
def final? klass
|
83
|
+
@classes.include? klass
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# @private
|
88
|
+
def inherited klass # rubocop:disable Lint/MissingSuper
|
89
|
+
raise Error.new(final_class: self, subclasses: [klass])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FeatureEnvy
|
4
|
+
# @private
|
5
|
+
module Internal
|
6
|
+
# Returns all subclasses of the given class.
|
7
|
+
def self.subclasses parent_class
|
8
|
+
subclasses = []
|
9
|
+
ObjectSpace.each_object(Class) do |klass|
|
10
|
+
subclasses << klass if klass.superclass.equal? parent_class
|
11
|
+
end
|
12
|
+
subclasses
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns a user-friendly class name.
|
16
|
+
def self.class_name klass
|
17
|
+
klass.name || ANONYMOUS_CLASS_NAME
|
18
|
+
end
|
19
|
+
|
20
|
+
ANONYMOUS_CLASS_NAME = "anonymous class"
|
21
|
+
private_constant :ANONYMOUS_CLASS_NAME
|
22
|
+
end
|
23
|
+
end
|
data/lib/feature_envy.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "feature_envy/version"
|
4
|
+
|
5
|
+
# Features inspired by other programming languages.
|
6
|
+
#
|
7
|
+
# Features are independent from each other and are implemented in separate
|
8
|
+
# submodules. Refer to module documentation for details on how each feature can
|
9
|
+
# be enabled and used.
|
10
|
+
module FeatureEnvy
|
11
|
+
# A base class for all errors raised by Feature Envy.
|
12
|
+
class Error < StandardError; end
|
13
|
+
|
14
|
+
autoload :Internal, "feature_envy/internal"
|
15
|
+
autoload :FinalClass, "feature_envy/final_class"
|
16
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
class FinalClassTest < Minitest::Test
|
6
|
+
using FeatureEnvy::FinalClass
|
7
|
+
|
8
|
+
def test_non_refinement_final_class_cannot_be_inherited
|
9
|
+
user = Class.new do
|
10
|
+
extend FeatureEnvy::FinalClass
|
11
|
+
end
|
12
|
+
|
13
|
+
assert_raises FeatureEnvy::FinalClass::Error,
|
14
|
+
"Subclassing a final class should have raised an exception" do
|
15
|
+
Class.new user
|
16
|
+
end
|
17
|
+
assert user.final?,
|
18
|
+
"A final class should have been reported as final but was not"
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_refinement_final_class_cannot_be_inherited
|
22
|
+
user = Class.new do
|
23
|
+
final!
|
24
|
+
end
|
25
|
+
|
26
|
+
assert_raises FeatureEnvy::FinalClass::Error,
|
27
|
+
"Subclassing a final class should have raised an exception" do
|
28
|
+
Class.new user
|
29
|
+
end
|
30
|
+
assert user.final?,
|
31
|
+
"A final class should have been reported as final but was not"
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_error_when_superclass_made_final
|
35
|
+
model = Class.new
|
36
|
+
user = Class.new model # rubocop:disable Lint/UselessAssignment
|
37
|
+
|
38
|
+
assert_raises FeatureEnvy::FinalClass::Error,
|
39
|
+
"Making a superclass final should have raised an exception" do
|
40
|
+
model.extend FeatureEnvy::FinalClass
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
class InternalTest < Minitest::Test
|
6
|
+
class Model; end
|
7
|
+
class User < Model; end
|
8
|
+
class Project < Model; end
|
9
|
+
class AdminUser < User; end
|
10
|
+
|
11
|
+
def test_subclasses
|
12
|
+
assert_equal [User, Project].sort_by(&:name),
|
13
|
+
FeatureEnvy::Internal.subclasses(Model).sort_by(&:name),
|
14
|
+
"All subclasses and no other descendants should have been returned"
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_class_name
|
18
|
+
assert_equal "InternalTest::Model",
|
19
|
+
FeatureEnvy::Internal.class_name(Model)
|
20
|
+
assert_equal "InternalTest::User",
|
21
|
+
FeatureEnvy::Internal.class_name(User)
|
22
|
+
assert_equal "anonymous class",
|
23
|
+
FeatureEnvy::Internal.class_name(Class.new)
|
24
|
+
end
|
25
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: feature_envy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Greg Navis
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-03-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 13.0.6
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 13.0.6
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.18.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.18.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.21.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.21.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop-minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.26.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.26.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop-rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.6.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.6.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: yard
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.9.28
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.9.28
|
97
|
+
description:
|
98
|
+
email: contact@gregnavis.com
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files: []
|
102
|
+
files:
|
103
|
+
- README.md
|
104
|
+
- lib/feature_envy.rb
|
105
|
+
- lib/feature_envy/final_class.rb
|
106
|
+
- lib/feature_envy/internal.rb
|
107
|
+
- lib/feature_envy/version.rb
|
108
|
+
- test/final_class_test.rb
|
109
|
+
- test/internal_test.rb
|
110
|
+
- test/support/assertions.rb
|
111
|
+
- test/test_helper.rb
|
112
|
+
homepage: https://github.com/gregnavis/feature_envy
|
113
|
+
licenses:
|
114
|
+
- MIT
|
115
|
+
metadata:
|
116
|
+
homepage_uri: https://github.com/gregnavis/feature_envy
|
117
|
+
source_code_uri: https://github.com/gregnavis/feature_envy
|
118
|
+
rubygems_mfa_required: 'true'
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: 2.5.0
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
requirements: []
|
134
|
+
rubygems_version: 3.4.6
|
135
|
+
signing_key:
|
136
|
+
specification_version: 4
|
137
|
+
summary: Feature Envy enhances Ruby with features inspired by other programming languages
|
138
|
+
test_files:
|
139
|
+
- test/final_class_test.rb
|
140
|
+
- test/internal_test.rb
|
141
|
+
- test/support/assertions.rb
|
142
|
+
- test/test_helper.rb
|