feature_envy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FeatureEnvy
4
+ # The current version number.
5
+ VERSION = "0.1.0"
6
+ end
@@ -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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Assertions
4
+ def assert_mapping callable, mapping
5
+ Hash(mapping).each do |input, output|
6
+ arguments = input.is_a?(Array) ? input : [input]
7
+
8
+ assert_equal output, callable.call(*arguments)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4
+ require "feature_envy"
5
+
6
+ require "minitest/autorun"
7
+
8
+ require_relative "support/assertions"
9
+
10
+ class Minitest::Test
11
+ include Assertions
12
+ end
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