mixins 0.1.0.pre.20250527171116

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: 931a539b8bc837e347f9e8daaaa3bbf457f48e4c320512be024bc591852f6ddd
4
+ data.tar.gz: 94ae6c7269ce07c2578499e51da76641b2b4df11caa5f76bc4dbd691ae61d43e
5
+ SHA512:
6
+ metadata.gz: 25a3c87aa1e640ecc6f9b05c48717434d184a0987b35b78e38c19c5c57b399e2f63be87be3239e56f01800bd17dadefe79dfaabf0cad9b3e4274513f473fe2fa
7
+ data.tar.gz: 56b6973f4de6917808a36e134575fe30c125f3ac6a9e03f3b507f4b2cae7c8b42ae40d032500d8c8e7f01d106c94811945e2dbd775ca70a2951eba9e141ae239
checksums.yaml.gz.sig ADDED
Binary file
data/History.md ADDED
@@ -0,0 +1,7 @@
1
+ # Release History for mixins
2
+
3
+ ---
4
+
5
+ ## v0.0.1 [2025-05-27] Michael Granger <ged@faeriemud.org>
6
+
7
+ First release.
data/LICENSE.txt ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2025, Michael Granger
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
18
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # mixins
2
+
3
+ home
4
+ : https://hg.sr.ht/~ged/Mixins
5
+
6
+ code
7
+ : https://hg.sr.ht/~ged/Mixins
8
+
9
+ github
10
+ : https://github.com/ged/mixins
11
+
12
+ docs
13
+ : https://deveiate.org/code/mixins
14
+
15
+
16
+ ## Description
17
+
18
+ This is a collection of zero-dependency mixins. They're intended to be generically useful for building other software, well-tested, and not add any non-stdlib dependencies.
19
+
20
+ The current mixins are:
21
+
22
+ - Mixins::DataUtilities
23
+ - Mixins::Datadir
24
+ - Mixins::Delegation
25
+ - Mixins::Hooks
26
+ - Mixins::Inspection
27
+ - Mixins::MethodUtilities
28
+
29
+
30
+ ## Prerequisites
31
+
32
+ * Ruby 3+
33
+
34
+
35
+ ## Installation
36
+
37
+ $ gem install mixins
38
+
39
+
40
+ ## Contributing
41
+
42
+ You can check out the current development source with Mercurial via its
43
+ [project page](https://hg.sr.ht/~ged/Mixins). Or if you prefer Git, via
44
+ [its Github mirror](https://github.com/ged/mixins).
45
+
46
+ After checking out the source, run:
47
+
48
+ $ gem install -Ng
49
+ $ rake setup
50
+
51
+ This will install dependencies, and do any other necessary setup for development.
52
+
53
+
54
+ ## Authors
55
+
56
+ - Michael Granger <ged@faeriemud.org>
57
+
58
+
59
+ ## License
60
+
61
+ Copyright (c) 2025, Michael Granger
62
+ All rights reserved.
63
+
64
+ Redistribution and use in source and binary forms, with or without
65
+ modification, are permitted provided that the following conditions are met:
66
+
67
+ * Redistributions of source code must retain the above copyright notice,
68
+ this list of conditions and the following disclaimer.
69
+
70
+ * Redistributions in binary form must reproduce the above copyright notice,
71
+ this list of conditions and the following disclaimer in the documentation
72
+ and/or other materials provided with the distribution.
73
+
74
+ * Neither the name of the author/s, nor the names of the project's
75
+ contributors may be used to endorse or promote products derived from this
76
+ software without specific prior written permission.
77
+
78
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
79
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
80
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
81
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
82
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
83
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
84
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
85
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
86
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
87
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
88
+
89
+
@@ -0,0 +1,88 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'tempfile'
4
+
5
+ require 'mixins' unless defined?( Mixins )
6
+
7
+
8
+ # A collection of miscellaneous functions that are useful for manipulating
9
+ # complex data structures.
10
+ #
11
+ # include Ravn::DataUtilities
12
+ # newhash = deep_copy( oldhash )
13
+ #
14
+ module Mixins::DataUtilities
15
+
16
+ ###############
17
+ module_function
18
+ ###############
19
+
20
+
21
+ ### Recursively copy the specified +obj+ and return the result.
22
+ def deep_copy( obj )
23
+
24
+ # Handle mocks during testing
25
+ return obj if obj.class.name == 'RSpec::Mocks::Mock'
26
+
27
+ # rubocop:disable Layout/IndentationWidth, Layout/IndentationStyle
28
+ return case obj
29
+ when NilClass, Numeric, TrueClass, FalseClass, Symbol,
30
+ Module, Encoding, IO, Tempfile
31
+ obj
32
+
33
+ when Array
34
+ obj.map {|o| deep_copy(o) }
35
+
36
+ when Hash
37
+ newhash = {}
38
+ newhash.default_proc = obj.default_proc if obj.default_proc
39
+ obj.each do |k,v|
40
+ newhash[ deep_copy(k) ] = deep_copy( v )
41
+ end
42
+ newhash
43
+
44
+ else
45
+ obj.clone
46
+ end
47
+ # rubocop:enable Layout/IndentationWidth, Layout/IndentationStyle
48
+ end
49
+
50
+
51
+ ### Return a duplicate of the given +object+ with its Symbol keys transformed
52
+ ### into Strings.
53
+ def stringify_keys( object )
54
+ case object
55
+ when Hash
56
+ return object.each_with_object( {} ) do |(key,val), newhash|
57
+ key = key.to_s if key.is_a?( Symbol )
58
+ newhash[ key ] = stringify_keys( val )
59
+ end
60
+ when Array
61
+ return object.map {|el| stringify_keys(el) }
62
+ else
63
+ return object
64
+ end
65
+ end
66
+
67
+
68
+ ### Return a duplicate of the given +object+ with its identifier-like String keys
69
+ ### transformed into Symbols.
70
+ def symbolify_keys( object )
71
+ case object
72
+ when Hash
73
+ return object.each_with_object( {} ) do |(key,val), newhash|
74
+ key = key.to_sym if key.respond_to?( :to_sym ) &&
75
+ key.match?( /\A\w+\Z/ )
76
+ newhash[ key ] = symbolify_keys( val )
77
+ end
78
+ when Array
79
+ return object.map {|el| symbolify_keys(el) }
80
+ else
81
+ return object
82
+ end
83
+ end
84
+ alias_method :internify_keys, :symbolify_keys
85
+
86
+ end # module Mixins::DataUtilities
87
+
88
+
@@ -0,0 +1,59 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'pathname'
4
+ require 'rubygems'
5
+
6
+ require 'mixins' unless defined?( Mixins )
7
+
8
+
9
+ # When extended in a class, automatically set the path to a DATA_DIR
10
+ # constant, derived from the class name. Prefers environmental
11
+ # override, Gem path, then local filesystem pathing.
12
+ #
13
+ # This can also be called manually if the including class name doesn't
14
+ # match the gem, or something else esoteric.
15
+ #
16
+ # DATA_DIR is a Pathname object.
17
+ #
18
+ module Mixins::Datadir
19
+
20
+ ### Extend hook: Set the DATA_DIR constant in the extending
21
+ ### class.
22
+ ###
23
+ def self::extended( obj )
24
+ name = obj.name.downcase.gsub( '::', '-' )
25
+ dir = self.find_datadir( name )
26
+
27
+ obj.const_set( :DATA_DIR, dir )
28
+ obj.singleton_class.attr_accessor :data_dir
29
+ obj.data_dir = dir
30
+ end
31
+
32
+
33
+ ### Return a pathname object for the extended class DATA_DIR. This
34
+ ### allows for the DATA_DIR constant to be used transparently between
35
+ ### development (local checkout) and production (gem installation)
36
+ ### environments.
37
+ ###
38
+ def self::find_datadir( gemname, env: nil )
39
+ unless env
40
+ comps = gemname.split( '-' )
41
+ env = comps.size > 1 ? comps.last : comps.first
42
+ env = "%s_DATADIR" % [ env.upcase ]
43
+ end
44
+
45
+ loaded_gemspec = Gem.loaded_specs[ gemname ]
46
+
47
+ dir = if ENV[ env ]
48
+ Pathname( ENV[ env ] )
49
+ elsif loaded_gemspec && File.exist?( loaded_gemspec.datadir )
50
+ Pathname( loaded_gemspec.datadir )
51
+ else
52
+ caller_path = caller_locations( 2, 1 ).first.absolute_path
53
+ Pathname( caller_path ).dirname.parent.parent + "data/#{gemname}"
54
+ end
55
+
56
+ return dir
57
+ end
58
+
59
+ end # module Mixins::Datadir
@@ -0,0 +1,95 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'mixins' unless defined?( Mixins )
4
+
5
+
6
+ # A collection of various delegation code-generators that can be used to define
7
+ # delegation through other methods, to instance variables, etc.
8
+ module Mixins::Delegation
9
+
10
+ ### Define the given +delegated_methods+ as delegators to the like-named method
11
+ ### of the return value of the +delegate_method+.
12
+ ###
13
+ ### class MyClass
14
+ ### extend Strelka::Delegation
15
+ ###
16
+ ### # Delegate the #bound?, #err, and #result2error methods to the connection
17
+ ### # object returned by the #connection method. This allows the connection
18
+ ### # to still be loaded on demand/overridden/etc.
19
+ ### def_method_delegators :connection, :bound?, :err, :result2error
20
+ ###
21
+ ### def connection
22
+ ### @connection ||= self.connect
23
+ ### end
24
+ ### end
25
+ ###
26
+ def def_method_delegators( delegate_method, *delegated_methods )
27
+ delegated_methods.each do |name|
28
+ body = Mixins::Delegation.make_method_delegator( delegate_method, name )
29
+ define_method( name, &body )
30
+ end
31
+ end
32
+
33
+
34
+ ### Define the given +delegated_methods+ as delegators to the like-named method
35
+ ### of the specified +ivar+. This is pretty much identical with how 'Forwardable'
36
+ ### from the stdlib does delegation, but it's reimplemented here for consistency.
37
+ ###
38
+ ### class MyClass
39
+ ### extend Strelka::Delegation
40
+ ###
41
+ ### # Delegate the #each method to the @collection ivar
42
+ ### def_ivar_delegators :@collection, :each
43
+ ###
44
+ ### end
45
+ ###
46
+ def def_ivar_delegators( ivar, *delegated_methods )
47
+ delegated_methods.each do |name|
48
+ body = Mixins::Delegation.make_ivar_delegator( ivar, name )
49
+ define_method( name, &body )
50
+ end
51
+ end
52
+
53
+
54
+ ### Define the given +delegated_methods+ as delegators to the like-named class
55
+ ### method.
56
+ def def_class_delegators( *delegated_methods )
57
+ delegated_methods.each do |name|
58
+ define_method( name ) do |*args|
59
+ self.class.__send__( name, *args )
60
+ end
61
+ end
62
+ end
63
+
64
+
65
+ ###############
66
+ module_function
67
+ ###############
68
+
69
+ ### Make the body of a delegator method that will delegate to the +name+ method
70
+ ### of the object returned by the +delegate+ method.
71
+ def make_method_delegator( delegate, name )
72
+ return ->( *args, **kwargs, &block ) do
73
+ self.__send__( delegate ).__send__( name, *args, **kwargs, &block )
74
+ rescue => err
75
+ bt = err.backtrace_locations
76
+ bt.shift
77
+ raise( err, err.message, bt, cause: err )
78
+ end
79
+ end
80
+
81
+
82
+ ### Make the body of a delegator method that will delegate calls to the +name+
83
+ ### method to the given +ivar+.
84
+ def make_ivar_delegator( ivar_name, name )
85
+ return ->( *args, **kwargs, &block ) do
86
+ ivar = self.instance_variable_get( ivar_name )
87
+ ivar.__send__( name, *args, **kwargs, &block )
88
+ rescue => err
89
+ bt = err.backtrace_locations
90
+ bt.shift
91
+ raise( err, err.message, bt, cause: err )
92
+ end
93
+ end
94
+
95
+ end # module Delegation
@@ -0,0 +1,84 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'mixins' unless defined?( Mixins )
4
+
5
+
6
+ # Methods for declaring hook methods.
7
+ #
8
+ # class MyClass
9
+ # extend Mixins::Hooks
10
+ #
11
+ # define_hook :before_fork
12
+ # define_hook :after_fork
13
+ # end
14
+ #
15
+ # MyClass.before_fork do
16
+ # @socket.close
17
+ # end
18
+ # MyClass.after_fork do
19
+ # @socket = Socket.new
20
+ # end
21
+ #
22
+ # MyClass.run_before_fork_hook
23
+ # fork do
24
+ # MyClass.run_after_fork_hook
25
+ # end
26
+ #
27
+ #
28
+ module Mixins::Hooks
29
+
30
+ ### Extension callback -- also extend it with MethodUtilities
31
+ def self::extended( obj )
32
+ super
33
+ obj.extend( Mixins::MethodUtilities )
34
+ end
35
+
36
+
37
+ ### Create the body of a method that can register a callback for the specified +hook+.
38
+ def self::make_registration_method( callbackset, **options )
39
+ return lambda do |&callback|
40
+ raise LocalJumpError, "no callback given" unless callback
41
+ set = self.public_send( callbackset ) or raise "No hook registration set!"
42
+ set.add( callback )
43
+
44
+ callback.call if self.public_send( "#{callbackset}_run?" )
45
+ end
46
+ end
47
+
48
+
49
+ ### Create the body of a method that calls the callbacks of the given +hook+.
50
+ def self::make_hook_method( callbackset, **options )
51
+ return lambda do |*args|
52
+ set = self.public_send( callbackset ) or raise "No hook callback registration set!"
53
+
54
+ self.public_send( "#{callbackset}_run=", true )
55
+
56
+ set.to_a.each do |callback|
57
+ callback.call( *args )
58
+ end
59
+ end
60
+ end
61
+
62
+
63
+ ### Define a hook with the given +name+ that can be registered by calling the
64
+ ### method of the same +name+ and then run by calling #call_<name>_hooks.
65
+ def define_hook( name, **options )
66
+ callbacks_name = "#{name}_callbacks"
67
+ self.instance_variable_set( "@#{callbacks_name}", Set.new )
68
+ self.singleton_attr_reader( callbacks_name )
69
+ self.instance_variable_set( "@#{callbacks_name}_run", false )
70
+ self.singleton_predicate_accessor( "#{callbacks_name}_run" )
71
+
72
+ register_body = Mixins::Hooks.
73
+ make_registration_method( callbacks_name, **options )
74
+ self.singleton_class.define_method( name, &register_body )
75
+
76
+ calling_body = Mixins::Hooks.
77
+ make_hook_method( callbacks_name, **options )
78
+ self.singleton_class.define_method( "call_#{name}_hook", &calling_body )
79
+ self.singleton_method_alias( "run_#{name}_hook", "call_#{name}_hook" )
80
+ end
81
+
82
+ end # module Mixins::Hooks
83
+
84
+
@@ -0,0 +1,28 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'mixins' unless defined?( Mixins )
4
+
5
+
6
+ # An extensible #inspect.
7
+ module Mixins::Inspection
8
+
9
+ ### Return a human-readable representation of the object suitable for debugging.
10
+ def inspect
11
+ details = self.inspect_details
12
+ details = ' ' + details unless details.empty? || details.start_with?( ' ' )
13
+
14
+ return "#<%p:#%x%s>" % [
15
+ self.class,
16
+ self.object_id,
17
+ details,
18
+ ]
19
+ end
20
+
21
+
22
+ ### Return the detail portion of the inspect output for this object.
23
+ def inspect_details
24
+ return ''
25
+ end
26
+
27
+ end # module Mixins::Inspection
28
+
@@ -0,0 +1,92 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'mixins' unless defined?( Mixins )
4
+
5
+
6
+ # A collection of methods for declaring other methods.
7
+ #
8
+ # class MyClass
9
+ # extend Mixins::MethodUtilities
10
+ #
11
+ # singleton_attr_accessor :types
12
+ # singleton_method_alias :kinds, :types
13
+ # end
14
+ #
15
+ # MyClass.types = [ :pheno, :proto, :stereo ]
16
+ # MyClass.kinds # => [:pheno, :proto, :stereo]
17
+ #
18
+ module Mixins::MethodUtilities
19
+
20
+ ### Creates instance variables and corresponding methods that return their
21
+ ### values for each of the specified +symbols+ in the singleton of the
22
+ ### declaring object (e.g., class instance variables and methods if declared
23
+ ### in a Class).
24
+ def singleton_attr_reader( *symbols )
25
+ singleton_class.instance_exec( symbols ) do |attrs|
26
+ attr_reader( *attrs )
27
+ end
28
+ end
29
+
30
+
31
+ ### Create instance variables and corresponding methods that return
32
+ ### true or false values for each of the specified +symbols+ in the singleton
33
+ ### of the declaring object.
34
+ def singleton_predicate_reader( *symbols )
35
+ singleton_class.extend( Mixins::MethodUtilities )
36
+ singleton_class.attr_predicate( *symbols )
37
+ end
38
+
39
+
40
+ ### Creates methods that allow assignment to the attributes of the singleton
41
+ ### of the declaring object that correspond to the specified +symbols+.
42
+ def singleton_attr_writer( *symbols )
43
+ singleton_class.instance_exec( symbols ) do |attrs|
44
+ attr_writer( *attrs )
45
+ end
46
+ end
47
+
48
+
49
+ ### Creates readers and writers that allow assignment to the attributes of
50
+ ### the singleton of the declaring object that correspond to the specified
51
+ ### +symbols+.
52
+ def singleton_attr_accessor( *symbols )
53
+ symbols.each do |sym|
54
+ singleton_class.__send__( :attr_accessor, sym )
55
+ end
56
+ end
57
+
58
+
59
+ ### Create predicate methods and writers that allow assignment to the attributes
60
+ ### of the singleton of the declaring object that correspond to the specified
61
+ ### +symbols+.
62
+ def singleton_predicate_accessor( *symbols )
63
+ singleton_class.extend( Mixins::MethodUtilities )
64
+ singleton_class.attr_predicate_accessor( *symbols )
65
+ end
66
+
67
+
68
+ ### Creates an alias for the +original+ method named +newname+.
69
+ def singleton_method_alias( newname, original )
70
+ singleton_class.__send__( :alias_method, newname, original )
71
+ end
72
+
73
+
74
+ ### Create a reader in the form of a predicate for the given +attrname+.
75
+ def attr_predicate( attrname )
76
+ attrname = attrname.to_s.chomp( '?' )
77
+ define_method( "#{attrname}?" ) do
78
+ instance_variable_get( "@#{attrname}" ) ? true : false
79
+ end
80
+ end
81
+
82
+
83
+ ### Create a reader in the form of a predicate for the given +attrname+
84
+ ### as well as a regular writer method.
85
+ def attr_predicate_accessor( attrname )
86
+ attrname = attrname.to_s.chomp( '?' )
87
+ attr_writer( attrname )
88
+ attr_predicate( attrname )
89
+ end
90
+
91
+ end # module Mixins::MethodUtilities
92
+
data/lib/mixins.rb ADDED
@@ -0,0 +1,20 @@
1
+ # -*- ruby -*-
2
+
3
+
4
+ # A collection of Modules that can be mixed into other modules to make
5
+ # common tasks easier and more intention-revealing.
6
+ module Mixins
7
+
8
+ # Package version
9
+ VERSION = '0.0.1'
10
+
11
+
12
+ autoload :MethodUtilities, 'mixins/method_utilities'
13
+ autoload :DataUtilities, 'mixins/data_utilities'
14
+ autoload :Delegation, 'mixins/delegation'
15
+ autoload :Hooks, 'mixins/hooks'
16
+ autoload :Inspection, 'mixins/inspection'
17
+ autoload :Datadir, 'mixins/datadir'
18
+
19
+ end # module Mixins
20
+