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 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