musa-dsl 0.30.2 → 0.41.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 +4 -4
- data/.gitignore +5 -1
- data/.version +6 -0
- data/.yardopts +7 -0
- data/Gemfile +0 -1
- data/README.md +227 -6
- data/docs/README.md +83 -0
- data/docs/api-reference.md +86 -0
- data/docs/getting-started/quick-start.md +93 -0
- data/docs/getting-started/tutorial.md +58 -0
- data/docs/subsystems/core-extensions.md +316 -0
- data/docs/subsystems/datasets.md +465 -0
- data/docs/subsystems/generative.md +290 -0
- data/docs/subsystems/matrix.md +63 -0
- data/docs/subsystems/midi.md +123 -0
- data/docs/subsystems/music.md +544 -0
- data/docs/subsystems/musicxml-builder.md +264 -0
- data/docs/subsystems/neumas.md +71 -0
- data/docs/subsystems/repl.md +135 -0
- data/docs/subsystems/sequencer.md +98 -0
- data/docs/subsystems/series.md +302 -0
- data/docs/subsystems/transcription.md +152 -0
- data/docs/subsystems/transport.md +177 -0
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +68 -0
- data/lib/musa-dsl/core-ext/arrayfy.rb +110 -0
- data/lib/musa-dsl/core-ext/attribute-builder.rb +91 -30
- data/lib/musa-dsl/core-ext/deep-copy.rb +125 -2
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +78 -0
- data/lib/musa-dsl/core-ext/extension.rb +53 -0
- data/lib/musa-dsl/core-ext/hashify.rb +162 -1
- data/lib/musa-dsl/core-ext/inspect-nice.rb +154 -0
- data/lib/musa-dsl/core-ext/smart-proc-binder.rb +117 -0
- data/lib/musa-dsl/core-ext/with.rb +114 -0
- data/lib/musa-dsl/datasets/dataset.rb +109 -0
- data/lib/musa-dsl/datasets/delta-d.rb +78 -0
- data/lib/musa-dsl/datasets/e.rb +186 -2
- data/lib/musa-dsl/datasets/gdv.rb +279 -2
- data/lib/musa-dsl/datasets/gdvd.rb +201 -0
- data/lib/musa-dsl/datasets/helper.rb +75 -0
- data/lib/musa-dsl/datasets/p.rb +177 -2
- data/lib/musa-dsl/datasets/packed-v.rb +91 -0
- data/lib/musa-dsl/datasets/pdv.rb +136 -1
- data/lib/musa-dsl/datasets/ps.rb +134 -4
- data/lib/musa-dsl/datasets/score/queriable.rb +143 -1
- data/lib/musa-dsl/datasets/score/render.rb +105 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +138 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +111 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +200 -1
- data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +145 -1
- data/lib/musa-dsl/datasets/score.rb +279 -0
- data/lib/musa-dsl/datasets/v.rb +88 -0
- data/lib/musa-dsl/generative/darwin.rb +215 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +387 -0
- data/lib/musa-dsl/generative/markov.rb +135 -3
- data/lib/musa-dsl/generative/rules.rb +312 -4
- data/lib/musa-dsl/generative/variatio.rb +286 -2
- data/lib/musa-dsl/logger/logger.rb +267 -2
- data/lib/musa-dsl/matrix/matrix.rb +256 -10
- data/lib/musa-dsl/midi/midi-recorder.rb +113 -2
- data/lib/musa-dsl/midi/midi-voices.rb +275 -4
- data/lib/musa-dsl/music/chord-definition.rb +233 -1
- data/lib/musa-dsl/music/chord-definitions.rb +33 -6
- data/lib/musa-dsl/music/chords.rb +353 -2
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +70 -206
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_dominant_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_major_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_minor_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/blues/blues_major_scale_kind.rb +100 -0
- data/lib/musa-dsl/music/scale_kinds/blues/blues_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_kinds/chromatic_scale_kind.rb +79 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/double_harmonic_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/hungarian_minor_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_major_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_minor_scale_kind.rb +101 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/phrygian_dominant_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/harmonic_major/harmonic_major_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/major_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/altered_scale_kind.rb +106 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/dorian_b2_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/locrian_sharp2_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_augmented_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_dominant_scale_kind.rb +106 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/melodic_minor_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/mixolydian_b6_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/minor_harmonic_scale_kind.rb +125 -0
- data/lib/musa-dsl/music/scale_kinds/minor_natural_scale_kind.rb +123 -0
- data/lib/musa-dsl/music/scale_kinds/modes/dorian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/locrian_scale_kind.rb +114 -0
- data/lib/musa-dsl/music/scale_kinds/modes/lydian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/mixolydian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/phrygian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_major_scale_kind.rb +93 -0
- data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_minor_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_hw_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_wh_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/whole_tone_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_systems/equally_tempered_12_tone_scale_system.rb +80 -0
- data/lib/musa-dsl/music/scale_systems/twelve_semitones_scale_system.rb +60 -0
- data/lib/musa-dsl/music/scales.rb +1384 -40
- data/lib/musa-dsl/musicxml/builder/attributes.rb +483 -3
- data/lib/musa-dsl/musicxml/builder/backup-forward.rb +166 -1
- data/lib/musa-dsl/musicxml/builder/direction.rb +243 -0
- data/lib/musa-dsl/musicxml/builder/helper.rb +240 -0
- data/lib/musa-dsl/musicxml/builder/measure.rb +284 -0
- data/lib/musa-dsl/musicxml/builder/note-complexities.rb +324 -8
- data/lib/musa-dsl/musicxml/builder/note.rb +285 -0
- data/lib/musa-dsl/musicxml/builder/part-group.rb +108 -1
- data/lib/musa-dsl/musicxml/builder/part.rb +139 -0
- data/lib/musa-dsl/musicxml/builder/pitched-note.rb +124 -0
- data/lib/musa-dsl/musicxml/builder/rest.rb +93 -0
- data/lib/musa-dsl/musicxml/builder/score-partwise.rb +276 -0
- data/lib/musa-dsl/musicxml/builder/typed-text.rb +62 -1
- data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +83 -0
- data/lib/musa-dsl/neumalang/neumalang.rb +675 -0
- data/lib/musa-dsl/neumas/array-to-neumas.rb +149 -0
- data/lib/musa-dsl/neumas/neuma-decoder.rb +253 -0
- data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +142 -2
- data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +82 -0
- data/lib/musa-dsl/neumas/neumas.rb +67 -0
- data/lib/musa-dsl/neumas/string-to-neumas.rb +233 -1
- data/lib/musa-dsl/repl/repl.rb +550 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +118 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +149 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +296 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +88 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +161 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +263 -0
- data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +173 -1
- data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +177 -0
- data/lib/musa-dsl/sequencer/base-sequencer.rb +710 -10
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +210 -0
- data/lib/musa-dsl/sequencer/timeslots.rb +79 -0
- data/lib/musa-dsl/series/array-to-serie.rb +37 -1
- data/lib/musa-dsl/series/base-series.rb +843 -5
- data/lib/musa-dsl/series/buffer-serie.rb +54 -0
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +64 -0
- data/lib/musa-dsl/series/main-serie-constructors.rb +398 -2
- data/lib/musa-dsl/series/main-serie-operations.rb +538 -16
- data/lib/musa-dsl/series/proxy-serie.rb +67 -0
- data/lib/musa-dsl/series/quantizer-serie.rb +57 -7
- data/lib/musa-dsl/series/queue-serie.rb +78 -0
- data/lib/musa-dsl/series/series-composer.rb +701 -0
- data/lib/musa-dsl/series/timed-serie.rb +473 -28
- data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +404 -1
- data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +118 -0
- data/lib/musa-dsl/transcription/from-gdv.rb +84 -1
- data/lib/musa-dsl/transcription/transcription.rb +265 -0
- data/lib/musa-dsl/transport/clock.rb +125 -0
- data/lib/musa-dsl/transport/dummy-clock.rb +89 -2
- data/lib/musa-dsl/transport/external-tick-clock.rb +91 -0
- data/lib/musa-dsl/transport/input-midi-clock.rb +133 -1
- data/lib/musa-dsl/transport/timer-clock.rb +183 -1
- data/lib/musa-dsl/transport/timer.rb +83 -0
- data/lib/musa-dsl/transport/transport.rb +318 -0
- data/lib/musa-dsl/version.rb +2 -1
- data/lib/musa-dsl.rb +132 -25
- data/musa-dsl.gemspec +25 -18
- metadata +158 -16
|
@@ -1,8 +1,98 @@
|
|
|
1
|
+
require_relative 'extension'
|
|
1
2
|
require_relative 'deep-copy'
|
|
2
3
|
|
|
3
4
|
module Musa
|
|
4
5
|
module Extension
|
|
6
|
+
# Refinement that converts objects, arrays, and hashes into hashes with specified keys.
|
|
7
|
+
#
|
|
8
|
+
# This refinement is crucial for normalizing parameters in the DSL, especially when
|
|
9
|
+
# dealing with musical events that can be specified in multiple formats (positional,
|
|
10
|
+
# hash-based, or mixed).
|
|
11
|
+
#
|
|
12
|
+
# ## Core Behavior
|
|
13
|
+
#
|
|
14
|
+
# - **Object**: Creates hash mapping all keys to the same value
|
|
15
|
+
# - **Array**: Maps keys to array elements in order (consuming array)
|
|
16
|
+
# - **Hash**: Filters and reorders according to specified keys
|
|
17
|
+
# - **Preserves singleton class modules** on hash results
|
|
18
|
+
#
|
|
19
|
+
# ## Use Cases
|
|
20
|
+
#
|
|
21
|
+
# - Converting positional parameters to named parameters
|
|
22
|
+
# - Normalizing mixed parameter formats in musical events
|
|
23
|
+
# - Extracting specific keys from larger hashes
|
|
24
|
+
# - Providing defaults for missing event attributes
|
|
25
|
+
#
|
|
26
|
+
# @example Basic object hashification
|
|
27
|
+
# using Musa::Extension::Hashify
|
|
28
|
+
#
|
|
29
|
+
# 100.hashify(keys: [:velocity, :duration])
|
|
30
|
+
# # => { velocity: 100, duration: 100 }
|
|
31
|
+
#
|
|
32
|
+
# @example Array to hash
|
|
33
|
+
# using Musa::Extension::Hashify
|
|
34
|
+
#
|
|
35
|
+
# [60, 100, 0.5].hashify(keys: [:pitch, :velocity, :duration])
|
|
36
|
+
# # => { pitch: 60, velocity: 100, duration: 0.5 }
|
|
37
|
+
#
|
|
38
|
+
# @example Hash filtering and reordering
|
|
39
|
+
# using Musa::Extension::Hashify
|
|
40
|
+
#
|
|
41
|
+
# { pitch: 60, velocity: 100, channel: 0, duration: 1 }
|
|
42
|
+
# .hashify(keys: [:pitch, :velocity])
|
|
43
|
+
# # => { pitch: 60, velocity: 100 }
|
|
44
|
+
#
|
|
45
|
+
# @example With defaults
|
|
46
|
+
# using Musa::Extension::Hashify
|
|
47
|
+
#
|
|
48
|
+
# [60].hashify(keys: [:pitch, :velocity, :duration], default: nil)
|
|
49
|
+
# # => { pitch: 60, velocity: nil, duration: nil }
|
|
50
|
+
#
|
|
51
|
+
# @example Musical event normalization
|
|
52
|
+
# using Musa::Extension::Hashify
|
|
53
|
+
#
|
|
54
|
+
# # User provides just a pitch
|
|
55
|
+
# 60.hashify(keys: [:pitch, :velocity], default: 64)
|
|
56
|
+
# # => { pitch: 60, velocity: 64 }
|
|
57
|
+
#
|
|
58
|
+
# # User provides array [pitch, velocity, duration]
|
|
59
|
+
# [62, 90, 0.5].hashify(keys: [:pitch, :velocity, :duration])
|
|
60
|
+
# # => { pitch: 62, velocity: 90, duration: 0.5 }
|
|
61
|
+
#
|
|
62
|
+
# @see Musa::Datasets Hash-based event structures
|
|
63
|
+
# @note This refinement must be activated with `using Musa::Extension::Hashify`
|
|
64
|
+
# @note Singleton class modules (dataset extensions) are preserved on hash results
|
|
65
|
+
#
|
|
66
|
+
# ## Methods Added
|
|
67
|
+
#
|
|
68
|
+
# ### Object
|
|
69
|
+
# - {Object#hashify} - Creates a hash mapping all specified keys to this object's value
|
|
70
|
+
#
|
|
71
|
+
# ### Array
|
|
72
|
+
# - {Array#hashify} - Maps array elements to hash keys in order, consuming the array
|
|
73
|
+
#
|
|
74
|
+
# ### Hash
|
|
75
|
+
# - {Hash#hashify} - Filters and reorders hash to include only specified keys, preserving modules
|
|
5
76
|
module Hashify
|
|
77
|
+
# @!method hashify(keys:, default: nil)
|
|
78
|
+
# Creates a hash mapping all specified keys to this object's value.
|
|
79
|
+
#
|
|
80
|
+
# Useful for broadcasting a single value across multiple attributes.
|
|
81
|
+
# Nil objects can be replaced with a default value.
|
|
82
|
+
#
|
|
83
|
+
# @note This method is added to Object via refinement. Requires `using Musa::Extension::Hashify`.
|
|
84
|
+
#
|
|
85
|
+
# @param keys [Array<Symbol>] keys for the resulting hash.
|
|
86
|
+
# @param default [Object, nil] value to use if self is nil.
|
|
87
|
+
#
|
|
88
|
+
# @return [Hash] hash with all keys mapped to self (or default if nil).
|
|
89
|
+
#
|
|
90
|
+
# @example Single value to multiple keys
|
|
91
|
+
# using Musa::Extension::Hashify
|
|
92
|
+
# 127.hashify(keys: [:velocity, :pressure])
|
|
93
|
+
# # => { velocity: 127, pressure: 127 }
|
|
94
|
+
class ::Object; end
|
|
95
|
+
|
|
6
96
|
refine Object do
|
|
7
97
|
def hashify(keys:, default: nil)
|
|
8
98
|
keys.collect do |key|
|
|
@@ -16,6 +106,36 @@ module Musa
|
|
|
16
106
|
end
|
|
17
107
|
end
|
|
18
108
|
|
|
109
|
+
# @!method hashify(keys:, default: nil)
|
|
110
|
+
# Maps array elements to hash keys in order, consuming the array.
|
|
111
|
+
#
|
|
112
|
+
# Elements are assigned to keys sequentially. If the array has fewer elements
|
|
113
|
+
# than keys, remaining keys get nil (or default). The array is cloned before
|
|
114
|
+
# consumption, so the original is unchanged.
|
|
115
|
+
#
|
|
116
|
+
# @note This method is added to Array via refinement. Requires `using Musa::Extension::Hashify`.
|
|
117
|
+
#
|
|
118
|
+
# @param keys [Array<Symbol>] keys for the resulting hash (in order).
|
|
119
|
+
# @param default [Object, nil] value for keys without corresponding array elements.
|
|
120
|
+
#
|
|
121
|
+
# @return [Hash] hash with keys mapped to array elements in order.
|
|
122
|
+
#
|
|
123
|
+
# @example Basic array mapping
|
|
124
|
+
# using Musa::Extension::Hashify
|
|
125
|
+
# [60, 100, 0.25].hashify(keys: [:pitch, :velocity, :duration])
|
|
126
|
+
# # => { pitch: 60, velocity: 100, duration: 0.25 }
|
|
127
|
+
#
|
|
128
|
+
# @example Fewer elements than keys
|
|
129
|
+
# using Musa::Extension::Hashify
|
|
130
|
+
# [60, 100].hashify(keys: [:pitch, :velocity, :duration], default: nil)
|
|
131
|
+
# # => { pitch: 60, velocity: 100, duration: nil }
|
|
132
|
+
#
|
|
133
|
+
# @example More elements than keys (extras ignored)
|
|
134
|
+
# using Musa::Extension::Hashify
|
|
135
|
+
# [60, 100, 0.5, :ignored].hashify(keys: [:pitch, :velocity])
|
|
136
|
+
# # => { pitch: 60, velocity: 100 }
|
|
137
|
+
class ::Array; end
|
|
138
|
+
|
|
19
139
|
refine Array do
|
|
20
140
|
def hashify(keys:, default: nil)
|
|
21
141
|
values = clone
|
|
@@ -23,10 +143,51 @@ module Musa
|
|
|
23
143
|
value = values.shift
|
|
24
144
|
[ key,
|
|
25
145
|
value.nil? ? default : value ]
|
|
26
|
-
end.
|
|
146
|
+
end.to_h
|
|
27
147
|
end
|
|
28
148
|
end
|
|
29
149
|
|
|
150
|
+
# @!method hashify(keys:, default: nil)
|
|
151
|
+
# Filters and reorders hash to include only specified keys, preserving modules.
|
|
152
|
+
#
|
|
153
|
+
# Creates a new hash with only the requested keys, in the order specified.
|
|
154
|
+
# Missing keys get nil (or default). Singleton class modules (like dataset
|
|
155
|
+
# extensions) are copied to the result.
|
|
156
|
+
#
|
|
157
|
+
# @note This method is added to Hash via refinement. Requires `using Musa::Extension::Hashify`.
|
|
158
|
+
#
|
|
159
|
+
# @param keys [Array<Symbol>] keys to include in result (order matters).
|
|
160
|
+
# @param default [Object, nil] value for keys not present in source hash.
|
|
161
|
+
#
|
|
162
|
+
# @return [Hash] new hash with specified keys, preserving singleton modules.
|
|
163
|
+
#
|
|
164
|
+
# @example Filtering keys
|
|
165
|
+
# using Musa::Extension::Hashify
|
|
166
|
+
# { pitch: 60, velocity: 100, channel: 0 }
|
|
167
|
+
# .hashify(keys: [:pitch, :velocity])
|
|
168
|
+
# # => { pitch: 60, velocity: 100 }
|
|
169
|
+
#
|
|
170
|
+
# @example Reordering keys
|
|
171
|
+
# using Musa::Extension::Hashify
|
|
172
|
+
# { velocity: 100, pitch: 60 }
|
|
173
|
+
# .hashify(keys: [:pitch, :velocity])
|
|
174
|
+
# # => { pitch: 60, velocity: 100 }
|
|
175
|
+
#
|
|
176
|
+
# @example Adding missing keys with default
|
|
177
|
+
# using Musa::Extension::Hashify
|
|
178
|
+
# { pitch: 60 }
|
|
179
|
+
# .hashify(keys: [:pitch, :velocity], default: 80)
|
|
180
|
+
# # => { pitch: 60, velocity: 80 }
|
|
181
|
+
#
|
|
182
|
+
# @example Preserving dataset modules
|
|
183
|
+
# using Musa::Extension::Hashify
|
|
184
|
+
# event = { pitch: 60, velocity: 100 }.extend(Musa::Datasets::AbsI)
|
|
185
|
+
# event.hashify(keys: [:pitch, :velocity])
|
|
186
|
+
# # Result also extended with AbsI
|
|
187
|
+
#
|
|
188
|
+
# @note Singleton class modules are preserved via DeepCopy.copy_singleton_class_modules
|
|
189
|
+
class ::Hash; end
|
|
190
|
+
|
|
30
191
|
refine Hash do
|
|
31
192
|
def hashify(keys:, default: nil)
|
|
32
193
|
keys.collect do |key|
|
|
@@ -1,6 +1,89 @@
|
|
|
1
|
+
require_relative 'extension'
|
|
2
|
+
|
|
1
3
|
module Musa
|
|
2
4
|
module Extension
|
|
5
|
+
# Refinements that provide more readable inspect/to_s output for Hash and Rational.
|
|
6
|
+
#
|
|
7
|
+
# These refinements improve readability of log output and debugging, especially
|
|
8
|
+
# important when working with musical data that heavily uses Rationals for timing
|
|
9
|
+
# and Hashes for event parameters.
|
|
10
|
+
#
|
|
11
|
+
# ## Changes
|
|
12
|
+
#
|
|
13
|
+
# - **Hash**: Compact syntax with symbol keys shown as `key: value`
|
|
14
|
+
# - **Rational**: Musical-friendly format like `3+1/4r` instead of `(13/4)`
|
|
15
|
+
# - **Configurable**: Rational display can switch between simple and detailed modes
|
|
16
|
+
#
|
|
17
|
+
# ## Use Cases
|
|
18
|
+
#
|
|
19
|
+
# - Improving log readability in musical applications
|
|
20
|
+
# - Debugging DSL expressions with cleaner output
|
|
21
|
+
# - Displaying musical time values (bars, durations) naturally
|
|
22
|
+
#
|
|
23
|
+
# @example Hash formatting
|
|
24
|
+
# using Musa::Extension::InspectNice
|
|
25
|
+
#
|
|
26
|
+
# { pitch: 60, velocity: 100 }.inspect
|
|
27
|
+
# # => "{ pitch: 60, velocity: 100 }"
|
|
28
|
+
# # Instead of: "{:pitch=>60, :velocity=>100}"
|
|
29
|
+
#
|
|
30
|
+
# @example Rational formatting (detailed mode)
|
|
31
|
+
# using Musa::Extension::InspectNice
|
|
32
|
+
#
|
|
33
|
+
# (5/4r).inspect # => "1+1/4r"
|
|
34
|
+
# (3/2r).inspect # => "1+1/2r"
|
|
35
|
+
# (2/1r).inspect # => "2r"
|
|
36
|
+
# (-3/4r).inspect # => "-3/4r"
|
|
37
|
+
#
|
|
38
|
+
# @example Rational formatting (simple mode)
|
|
39
|
+
# using Musa::Extension::InspectNice
|
|
40
|
+
#
|
|
41
|
+
# Rational.to_s_as_inspect = false
|
|
42
|
+
# (5/4r).to_s # => "5/4"
|
|
43
|
+
# (2/1r).to_s # => "2"
|
|
44
|
+
#
|
|
45
|
+
# @see Musa::Logger::Logger Uses these refinements for cleaner logs
|
|
46
|
+
# @note These refinements must be activated with `using Musa::Extension::InspectNice`
|
|
47
|
+
#
|
|
48
|
+
# ## Methods Added
|
|
49
|
+
#
|
|
50
|
+
# ### Hash
|
|
51
|
+
# - {Hash#inspect} - Compact, readable inspect output with symbol-key shorthand
|
|
52
|
+
# - {Hash#to_s} - Aliases to_s to inspect for consistency
|
|
53
|
+
#
|
|
54
|
+
# ### Rational (singleton class)
|
|
55
|
+
# - {Rational.to_s_as_inspect} - Controls whether Rational#to_s uses inspect format
|
|
56
|
+
#
|
|
57
|
+
# ### Rational
|
|
58
|
+
# - {Rational#inspect} - Musical-friendly inspect output for Rational numbers
|
|
59
|
+
# - {Rational#to_s} - String representation controlled by Rational.to_s_as_inspect
|
|
3
60
|
module InspectNice
|
|
61
|
+
# @!method inspect
|
|
62
|
+
# Provides compact, readable inspect output with symbol-key shorthand.
|
|
63
|
+
#
|
|
64
|
+
# Symbol keys are displayed as `key: value` (Ruby 2.0+ syntax) instead of
|
|
65
|
+
# `:key => value`. String/other keys use the fat arrow syntax.
|
|
66
|
+
#
|
|
67
|
+
# @note This method is added to Hash via refinement. Requires `using Musa::Extension::InspectNice`.
|
|
68
|
+
#
|
|
69
|
+
# @return [String] compact hash representation.
|
|
70
|
+
#
|
|
71
|
+
# @example Mixed keys
|
|
72
|
+
# using Musa::Extension::InspectNice
|
|
73
|
+
# { pitch: 60, 'name' => 'C4' }.inspect
|
|
74
|
+
# # => "{ pitch: 60, 'name' => 'C4' }"
|
|
75
|
+
class ::Hash; end
|
|
76
|
+
|
|
77
|
+
# @!method to_s
|
|
78
|
+
# Aliases to_s to inspect for consistency.
|
|
79
|
+
#
|
|
80
|
+
# @note This method is added to Hash via refinement. Requires `using Musa::Extension::InspectNice`.
|
|
81
|
+
#
|
|
82
|
+
# @return [String] compact hash representation.
|
|
83
|
+
#
|
|
84
|
+
# @see Hash#inspect
|
|
85
|
+
class ::Hash; end
|
|
86
|
+
|
|
4
87
|
refine Hash do
|
|
5
88
|
def inspect
|
|
6
89
|
all = collect { |key, value| [', ', key.is_a?(Symbol) ? key.to_s + ': ' : key.inspect + ' => ', value.inspect] }.flatten
|
|
@@ -12,10 +95,81 @@ module Musa
|
|
|
12
95
|
alias to_s inspect
|
|
13
96
|
end
|
|
14
97
|
|
|
98
|
+
# Adds configuration attribute to Rational singleton class.
|
|
99
|
+
#
|
|
100
|
+
# This allows global control of Rational#to_s behavior.
|
|
101
|
+
#
|
|
102
|
+
# @!attribute [rw] to_s_as_inspect
|
|
103
|
+
# Controls whether Rational#to_s uses inspect format.
|
|
104
|
+
#
|
|
105
|
+
# When true: to_s displays detailed format (e.g., "1+1/4r")
|
|
106
|
+
# When false/nil: to_s displays simple format (e.g., "5/4")
|
|
107
|
+
#
|
|
108
|
+
# @note This attribute is added to Rational's singleton class via refinement. Requires `using Musa::Extension::InspectNice`.
|
|
109
|
+
#
|
|
110
|
+
# @return [Boolean, nil] current mode
|
|
111
|
+
#
|
|
112
|
+
# @example Switching modes
|
|
113
|
+
# using Musa::Extension::InspectNice
|
|
114
|
+
# Rational.to_s_as_inspect = true
|
|
115
|
+
# (5/4r).to_s # => "1+1/4r"
|
|
116
|
+
class ::Rational; end
|
|
117
|
+
|
|
15
118
|
refine Rational.singleton_class do
|
|
16
119
|
attr_accessor :to_s_as_inspect
|
|
17
120
|
end
|
|
18
121
|
|
|
122
|
+
# @!method inspect(simple: nil)
|
|
123
|
+
# Provides musical-friendly inspect output for Rational numbers.
|
|
124
|
+
#
|
|
125
|
+
# Two modes:
|
|
126
|
+
# - **Simple**: Just numerator/denominator (e.g., "5/4", "2")
|
|
127
|
+
# - **Detailed**: Mixed number with 'r' suffix (e.g., "1+1/4r", "2r")
|
|
128
|
+
#
|
|
129
|
+
# The detailed format is particularly useful for musical time values,
|
|
130
|
+
# making expressions like "3+1/2r" (3.5 bars) immediately readable.
|
|
131
|
+
#
|
|
132
|
+
# @note This method is added to Rational via refinement. Requires `using Musa::Extension::InspectNice`.
|
|
133
|
+
#
|
|
134
|
+
# @param simple [Boolean, nil] if true, uses simple format; if false/nil, uses detailed.
|
|
135
|
+
#
|
|
136
|
+
# @return [String] formatted rational.
|
|
137
|
+
#
|
|
138
|
+
# @example Detailed format (default for inspect)
|
|
139
|
+
# using Musa::Extension::InspectNice
|
|
140
|
+
# (5/4r).inspect # => "1+1/4r"
|
|
141
|
+
# (7/4r).inspect # => "1+3/4r"
|
|
142
|
+
# (-3/2r).inspect # => "-1-1/2r"
|
|
143
|
+
# (8/4r).inspect # => "2r"
|
|
144
|
+
# (3/4r).inspect # => "3/4r"
|
|
145
|
+
#
|
|
146
|
+
# @example Simple format
|
|
147
|
+
# using Musa::Extension::InspectNice
|
|
148
|
+
# (5/4r).inspect(simple: true) # => "5/4"
|
|
149
|
+
# (8/4r).inspect(simple: true) # => "2"
|
|
150
|
+
class ::Rational; end
|
|
151
|
+
|
|
152
|
+
# @!method to_s
|
|
153
|
+
# Provides string representation, format controlled by Rational.to_s_as_inspect.
|
|
154
|
+
#
|
|
155
|
+
# Delegates to #inspect with the appropriate simple flag based on the
|
|
156
|
+
# global Rational.to_s_as_inspect setting.
|
|
157
|
+
#
|
|
158
|
+
# @note This method is added to Rational via refinement. Requires `using Musa::Extension::InspectNice`.
|
|
159
|
+
#
|
|
160
|
+
# @return [String] formatted rational.
|
|
161
|
+
#
|
|
162
|
+
# @example When to_s_as_inspect is true
|
|
163
|
+
# using Musa::Extension::InspectNice
|
|
164
|
+
# Rational.to_s_as_inspect = true
|
|
165
|
+
# (5/4r).to_s # => "1+1/4r"
|
|
166
|
+
#
|
|
167
|
+
# @example When to_s_as_inspect is false/nil
|
|
168
|
+
# using Musa::Extension::InspectNice
|
|
169
|
+
# Rational.to_s_as_inspect = false
|
|
170
|
+
# (5/4r).to_s # => "5/4"
|
|
171
|
+
class ::Rational; end
|
|
172
|
+
|
|
19
173
|
refine Rational do
|
|
20
174
|
def inspect(simple: nil)
|
|
21
175
|
value = self.abs
|
|
@@ -1,17 +1,75 @@
|
|
|
1
|
+
require_relative 'extension'
|
|
2
|
+
|
|
1
3
|
module Musa
|
|
2
4
|
module Extension
|
|
5
|
+
# Module providing smart parameter binding for Proc objects.
|
|
6
|
+
#
|
|
7
|
+
# SmartProcBinder analyzes a Proc's parameter signature and intelligently
|
|
8
|
+
# matches provided arguments to expected parameters, handling both positional
|
|
9
|
+
# and keyword arguments with proper rest parameter support.
|
|
10
|
+
#
|
|
11
|
+
# @see Musa::Extension::With Uses SmartProcBinder for parameter management
|
|
3
12
|
module SmartProcBinder
|
|
13
|
+
# Wrapper for Proc objects that provides intelligent parameter matching and binding.
|
|
14
|
+
#
|
|
15
|
+
# This class introspects a Proc's parameter list and provides methods to:
|
|
16
|
+
# - Determine which parameters the Proc accepts
|
|
17
|
+
# - Filter provided arguments to match the Proc's signature
|
|
18
|
+
# - Call the Proc with properly matched arguments
|
|
19
|
+
# - Optionally rescue and handle exceptions
|
|
20
|
+
#
|
|
21
|
+
# ## Parameter Types Handled
|
|
22
|
+
#
|
|
23
|
+
# - **:req, :opt**: Required and optional positional parameters
|
|
24
|
+
# - **:rest**: Splat parameter (*args)
|
|
25
|
+
# - **:key, :keyreq**: Optional and required keyword parameters
|
|
26
|
+
# - **:keyrest**: Double splat parameter (**kwargs)
|
|
27
|
+
#
|
|
28
|
+
# ## Use Cases
|
|
29
|
+
#
|
|
30
|
+
# - DSL methods that need flexible parameter passing
|
|
31
|
+
# - Builder patterns with variable block signatures
|
|
32
|
+
# - Wrapper methods that forward arguments intelligently
|
|
33
|
+
# - Error handling for DSL block execution
|
|
34
|
+
#
|
|
35
|
+
# @example Basic usage
|
|
36
|
+
# block = proc { |a, b, c:| [a, b, c] }
|
|
37
|
+
# binder = SmartProcBinder.new(block)
|
|
38
|
+
#
|
|
39
|
+
# binder.call(1, 2, 3, 4, c: 5, d: 6)
|
|
40
|
+
# # => [1, 2, 5]
|
|
41
|
+
# # Only passes parameters that match signature
|
|
42
|
+
#
|
|
43
|
+
# @example With rescue handling
|
|
44
|
+
# error_handler = proc { |e| puts "Error: #{e.message}" }
|
|
45
|
+
# binder = SmartProcBinder.new(block, on_rescue: error_handler)
|
|
46
|
+
#
|
|
47
|
+
# binder.call(invalid_args) # Calls error_handler instead of raising
|
|
48
|
+
#
|
|
49
|
+
# @example Checking parameter support
|
|
50
|
+
# binder.key?(:pitch) # => true/false
|
|
51
|
+
# binder.has_key?(:velocity) # => true/false
|
|
4
52
|
class SmartProcBinder
|
|
53
|
+
# Creates a new SmartProcBinder wrapping the given block.
|
|
54
|
+
#
|
|
55
|
+
# Introspects the block's parameters and categorizes them for later matching.
|
|
56
|
+
#
|
|
57
|
+
# @param block [Proc] the proc/block to wrap.
|
|
58
|
+
# @param on_rescue [Proc, nil] optional error handler called with exception
|
|
59
|
+
# if block execution fails.
|
|
5
60
|
def initialize(block, on_rescue: nil)
|
|
6
61
|
@block = block
|
|
7
62
|
@on_rescue = on_rescue
|
|
8
63
|
|
|
64
|
+
# Track keyword parameters by name
|
|
9
65
|
@key_parameters = {}
|
|
10
66
|
@has_key_rest = false
|
|
11
67
|
|
|
68
|
+
# Track positional parameter count
|
|
12
69
|
@value_parameters_count = 0
|
|
13
70
|
@has_value_rest = false
|
|
14
71
|
|
|
72
|
+
# Introspect block's parameter signature
|
|
15
73
|
block.parameters.each do |parameter|
|
|
16
74
|
@key_parameters[parameter[1]] = nil if parameter[0] == :key || parameter[0] == :keyreq
|
|
17
75
|
@has_key_rest = true if parameter[0] == :keyrest
|
|
@@ -21,18 +79,36 @@ module Musa
|
|
|
21
79
|
end
|
|
22
80
|
end
|
|
23
81
|
|
|
82
|
+
# Returns the wrapped Proc.
|
|
83
|
+
#
|
|
84
|
+
# @return [Proc] the original block.
|
|
24
85
|
def proc
|
|
25
86
|
@block
|
|
26
87
|
end
|
|
27
88
|
|
|
89
|
+
# Returns the parameter signature of the wrapped Proc.
|
|
90
|
+
#
|
|
91
|
+
# @return [Array<Array>] array of [type, name] pairs describing parameters.
|
|
92
|
+
# @example
|
|
93
|
+
# proc { |a, b, c:| }.parameters # => [[:req, :a], [:req, :b], [:key, :c]]
|
|
28
94
|
def parameters
|
|
29
95
|
@block.parameters
|
|
30
96
|
end
|
|
31
97
|
|
|
98
|
+
# Calls the wrapped Proc with smart parameter matching.
|
|
99
|
+
#
|
|
100
|
+
# @param value_parameters [Array] positional arguments.
|
|
101
|
+
# @param key_parameters [Hash] keyword arguments.
|
|
102
|
+
# @param block [Proc, nil] block to pass to wrapped Proc.
|
|
103
|
+
#
|
|
104
|
+
# @return [Object] result of calling the wrapped Proc.
|
|
32
105
|
def call(*value_parameters, **key_parameters, &block)
|
|
33
106
|
_call value_parameters, key_parameters, block
|
|
34
107
|
end
|
|
35
108
|
|
|
109
|
+
# Internal call implementation with error handling.
|
|
110
|
+
#
|
|
111
|
+
# @api private
|
|
36
112
|
def _call(value_parameters, key_parameters = {}, block = nil)
|
|
37
113
|
if @on_rescue
|
|
38
114
|
begin
|
|
@@ -63,16 +139,43 @@ module Musa
|
|
|
63
139
|
end
|
|
64
140
|
end
|
|
65
141
|
|
|
142
|
+
# Checks if the wrapped Proc accepts a specific keyword parameter.
|
|
143
|
+
#
|
|
144
|
+
# Returns true if the Proc has a keyword parameter with the given name,
|
|
145
|
+
# or if it has a **kwargs rest parameter that accepts any keyword.
|
|
146
|
+
#
|
|
147
|
+
# @param key [Symbol] keyword parameter name to check.
|
|
148
|
+
#
|
|
149
|
+
# @return [Boolean] true if key is accepted, false otherwise.
|
|
150
|
+
#
|
|
151
|
+
# @example
|
|
152
|
+
# proc { |a:, b:, **rest| }.key?(:a) # => true
|
|
153
|
+
# proc { |a:, b:, **rest| }.key?(:unknown) # => true (has **rest)
|
|
154
|
+
# proc { |a:, b:| }.key?(:unknown) # => false
|
|
66
155
|
def key?(key)
|
|
67
156
|
@has_key_rest || @key_parameters.include?(key)
|
|
68
157
|
end
|
|
69
158
|
|
|
70
159
|
alias_method :has_key?, :key?
|
|
71
160
|
|
|
161
|
+
# Filters arguments to match the Proc's signature.
|
|
162
|
+
#
|
|
163
|
+
# @param value_parameters [Array] positional arguments to filter.
|
|
164
|
+
# @param key_parameters [Hash] keyword arguments to filter.
|
|
165
|
+
#
|
|
166
|
+
# @return [Array<Array, Hash>] tuple of [filtered_positionals, filtered_keywords].
|
|
72
167
|
def apply(*value_parameters, **key_parameters)
|
|
73
168
|
_apply(value_parameters, key_parameters)
|
|
74
169
|
end
|
|
75
170
|
|
|
171
|
+
# Internal implementation of argument filtering.
|
|
172
|
+
#
|
|
173
|
+
# Logic:
|
|
174
|
+
# - Positional: takes first N values (or all if *rest present)
|
|
175
|
+
# - Keywords: includes only expected keys (or all if **rest present)
|
|
176
|
+
# - Pads positional with nils if needed
|
|
177
|
+
#
|
|
178
|
+
# @api private
|
|
76
179
|
def _apply(value_parameters, key_parameters)
|
|
77
180
|
value_parameters ||= []
|
|
78
181
|
key_parameters ||= {}
|
|
@@ -99,6 +202,20 @@ module Musa
|
|
|
99
202
|
return values_result, hash_result
|
|
100
203
|
end
|
|
101
204
|
|
|
205
|
+
# Returns a string representation of the SmartProcBinder for debugging.
|
|
206
|
+
#
|
|
207
|
+
# Shows the wrapped Proc's parameter signature and internal state, making
|
|
208
|
+
# it easy to understand what parameters the binder expects and how it will
|
|
209
|
+
# match arguments.
|
|
210
|
+
#
|
|
211
|
+
# @return [String] formatted string showing parameters and configuration.
|
|
212
|
+
#
|
|
213
|
+
# @example Inspecting binder state
|
|
214
|
+
# block = proc { |a, b, c:, **rest| }
|
|
215
|
+
# binder = SmartProcBinder.new(block)
|
|
216
|
+
# puts binder.inspect
|
|
217
|
+
# # => "SmartProcBinder: parameters = [[:req, :a], [:req, :b], [:key, :c], [:keyrest, :rest]]
|
|
218
|
+
# # key_parameters = {:c=>nil} has_rest = true"
|
|
102
219
|
def inspect
|
|
103
220
|
"SmartProcBinder: parameters = #{parameters} key_parameters = #{@key_parameters} has_rest = #{@has_key_rest}"
|
|
104
221
|
end
|
|
@@ -1,28 +1,142 @@
|
|
|
1
|
+
require_relative 'extension'
|
|
1
2
|
require_relative 'smart-proc-binder'
|
|
2
3
|
|
|
3
4
|
module Musa
|
|
4
5
|
module Extension
|
|
6
|
+
# Module providing the `with` method for flexible DSL block execution.
|
|
7
|
+
#
|
|
8
|
+
# The `with` method is a cornerstone of Musa DSL's builder pattern, allowing
|
|
9
|
+
# objects to execute blocks in either the object's context (for DSL-style
|
|
10
|
+
# configuration) or the caller's context (for traditional Ruby blocks).
|
|
11
|
+
#
|
|
12
|
+
# ## Context Switching Logic
|
|
13
|
+
#
|
|
14
|
+
# The method intelligently determines which context to use based on:
|
|
15
|
+
# 1. The `keep_block_context` parameter (explicit control)
|
|
16
|
+
# 2. Block parameters (implicit control via `_` parameter)
|
|
17
|
+
# 3. Whether parameters are passed to the block
|
|
18
|
+
#
|
|
19
|
+
# ## Modes of Operation
|
|
20
|
+
#
|
|
21
|
+
# - **DSL mode** (`instance_eval`): Block executes in object's context
|
|
22
|
+
# - Used when: no parameters, no `keep_block_context`, no `_` parameter
|
|
23
|
+
# - Enables: direct access to object's instance variables and methods
|
|
24
|
+
#
|
|
25
|
+
# - **Caller context mode** (`call` with self as `_`): Block keeps its context
|
|
26
|
+
# - Used when: block has `_` parameter, or `keep_block_context: true`
|
|
27
|
+
# - Enables: access to both contexts (object via `_`, caller's scope naturally)
|
|
28
|
+
#
|
|
29
|
+
# - **Hybrid mode** (`instance_exec`): Block in object context with parameters
|
|
30
|
+
# - Used when: parameters provided but `keep_block_context` not set
|
|
31
|
+
# - Enables: DSL-style access plus explicit parameters
|
|
32
|
+
#
|
|
33
|
+
# ## Use Cases
|
|
34
|
+
#
|
|
35
|
+
# - Builder pattern DSL methods
|
|
36
|
+
# - Configuration blocks that need object context
|
|
37
|
+
# - Flexible API supporting both DSL and traditional Ruby styles
|
|
38
|
+
# - Initializers that configure objects via blocks
|
|
39
|
+
#
|
|
40
|
+
# @example DSL mode (instance_eval)
|
|
41
|
+
# class Builder
|
|
42
|
+
# include Musa::Extension::With
|
|
43
|
+
#
|
|
44
|
+
# def initialize(&block)
|
|
45
|
+
# @items = []
|
|
46
|
+
# with(&block) if block
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# def add(item)
|
|
50
|
+
# @items << item
|
|
51
|
+
# end
|
|
52
|
+
# end
|
|
53
|
+
#
|
|
54
|
+
# builder = Builder.new do
|
|
55
|
+
# add :foo
|
|
56
|
+
# add :bar
|
|
57
|
+
# end
|
|
58
|
+
# # Block has direct access to #add method
|
|
59
|
+
#
|
|
60
|
+
# @example Caller context with _ parameter
|
|
61
|
+
# external_var = 42
|
|
62
|
+
#
|
|
63
|
+
# Builder.new do |_|
|
|
64
|
+
# _.add :foo
|
|
65
|
+
# puts external_var # Can access caller's variables
|
|
66
|
+
# end
|
|
67
|
+
# # Block keeps caller's context, object accessed via _
|
|
68
|
+
#
|
|
69
|
+
# @example With parameters
|
|
70
|
+
# class Builder
|
|
71
|
+
# def initialize(name, &block)
|
|
72
|
+
# @name = name
|
|
73
|
+
# with(name, &block) if block
|
|
74
|
+
# end
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
# Builder.new('test') do |name|
|
|
78
|
+
# # Has access to object's context AND receives name parameter
|
|
79
|
+
# puts @name # Works
|
|
80
|
+
# puts name # Also works
|
|
81
|
+
# end
|
|
82
|
+
#
|
|
83
|
+
# @example Explicit keep_block_context
|
|
84
|
+
# Builder.new do |obj|
|
|
85
|
+
# obj.add :item
|
|
86
|
+
# # Block explicitly keeps caller's context
|
|
87
|
+
# end
|
|
88
|
+
#
|
|
89
|
+
# @see SmartProcBinder Used internally for parameter management
|
|
90
|
+
# @see Musa::Datasets DSL builder methods use this extensively
|
|
5
91
|
module With
|
|
92
|
+
# Executes a block with flexible context and parameter handling.
|
|
93
|
+
#
|
|
94
|
+
# @param value_parameters [Array] positional parameters to pass to block.
|
|
95
|
+
# @param keep_block_context [Boolean, nil] explicit control of context switching:
|
|
96
|
+
# - `true`: always keep caller's context
|
|
97
|
+
# - `false`: always use object's context
|
|
98
|
+
# - `nil`: auto-detect based on `_` parameter
|
|
99
|
+
# @param key_parameters [Hash] keyword parameters to pass to block.
|
|
100
|
+
# @param block [Proc] block to execute.
|
|
101
|
+
#
|
|
102
|
+
# @return [Object] result of block execution.
|
|
103
|
+
#
|
|
104
|
+
# @note The `_` parameter is special: when present, it signals "keep caller's context"
|
|
105
|
+
# and receives `self` (the object) as its value.
|
|
106
|
+
# @note Uses SmartProcBinder internally to handle parameter matching.
|
|
6
107
|
def with(*value_parameters, keep_block_context: nil, **key_parameters, &block)
|
|
108
|
+
# Wrap block in SmartProcBinder for parameter introspection and management
|
|
7
109
|
smart_block = Musa::Extension::SmartProcBinder::SmartProcBinder.new(block)
|
|
8
110
|
|
|
111
|
+
# Check if first parameter is _ (underscore), which signals "keep caller's context"
|
|
9
112
|
send_self_as_underscore_parameter = smart_block.parameters[0][1] == :_ unless smart_block.parameters.empty?
|
|
10
113
|
|
|
114
|
+
# Determine effective context mode:
|
|
115
|
+
# 1. Use explicit keep_block_context if provided
|
|
116
|
+
# 2. Otherwise, use _ parameter presence as signal
|
|
117
|
+
# 3. Default to false (use object's context)
|
|
11
118
|
effective_keep_block_context = keep_block_context
|
|
12
119
|
effective_keep_block_context = send_self_as_underscore_parameter if effective_keep_block_context.nil?
|
|
13
120
|
effective_keep_block_context = false if effective_keep_block_context.nil?
|
|
14
121
|
|
|
122
|
+
# Match provided parameters to block's expected parameters
|
|
15
123
|
effective_value_parameters, effective_key_parameters = smart_block._apply(value_parameters, key_parameters)
|
|
16
124
|
|
|
125
|
+
# Execute block in appropriate context
|
|
17
126
|
if effective_keep_block_context
|
|
127
|
+
# Keep caller's context: call block normally
|
|
18
128
|
if send_self_as_underscore_parameter
|
|
129
|
+
# Pass self as first parameter (the _ parameter)
|
|
19
130
|
smart_block.call(self, *effective_value_parameters, **effective_key_parameters)
|
|
20
131
|
else
|
|
132
|
+
# Just pass the effective parameters
|
|
21
133
|
smart_block.call(*effective_value_parameters, **effective_key_parameters)
|
|
22
134
|
end
|
|
23
135
|
elsif effective_value_parameters.empty? && effective_key_parameters.empty?
|
|
136
|
+
# DSL mode: no parameters, execute in object's context
|
|
24
137
|
instance_eval &block
|
|
25
138
|
else
|
|
139
|
+
# Hybrid mode: execute in object's context with parameters
|
|
26
140
|
instance_exec *effective_value_parameters, **effective_key_parameters, &block
|
|
27
141
|
end
|
|
28
142
|
end
|