magic-decorator 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05eb0d1c9c1f08235269894f1ce906ca355d65239d9a708ad68078d9ebce7159
4
- data.tar.gz: 679f113b552348de25243303de4550cda0774dd5e284bd4ec64c94c79471aecf
3
+ metadata.gz: 910cb75b4dedfce18a494e39779b65821642feb96a24f3cda7228deea96ccd04
4
+ data.tar.gz: 98603bb63196fa955b4aec3172ff6326f5be10371b0fffb96404a9f5cd84a86b
5
5
  SHA512:
6
- metadata.gz: fb7bd90db8afc2235cc5a1b7f572b316a5494888a34a07052a7d77a2b20abc8d07444dca8e2e8436bb0a1dd6e8e61f8ddf15c80ed54ac5ee956a99e90bfbac4d
7
- data.tar.gz: ddac7d0a18af55a99a561812660847f6935dd849c42287dd397466c520c392742580331526b33b98efe9a64020a9622e6dee92e60e177f08c8c0a5c4851310c3
6
+ metadata.gz: 4b4bfb9bbb3838ac81dc35527caddd9d42d86887e1019ad75c7aad5f21af03607a1540523555aa0669baef7705a989af99487d3fa0a925fe8fedca96749070e3
7
+ data.tar.gz: 9f1cc4b1b608fd9b795b2a296c7919e513ed0a9ea7a9c0d1957fe23e5f5b537edc7ea30985ccc02ce72ca5fac1c7707d8b44769502a4f702e426dd26c2ce1260
data/CHANGELOG.md CHANGED
@@ -1,10 +1,31 @@
1
+ ## [0.2.0] — 2024-10-17
2
+
3
+ ### Changed
4
+
5
+ - For almost any method called on a decorated object, both its result and `yield`ed arguments get decorated.
6
+ Some methods aren’t meant to be decorated though:
7
+ - `deconstruct` & `deconstruct_keys` for _pattern matching_,
8
+ - _converting_ methods: those starting with `to_`,
9
+ - _system_ methods: those starting with `_`.
10
+
11
+ ### Added
12
+
13
+ - `Magic::Decorator::Base.undecorated` to exclude methods from being decorated automagically.
14
+
15
+ #### Default decorators
16
+
17
+ - `EnumerableDecorator` to decorate `Enumerable`s.
18
+ - enables _splat_ operator: `*decorated` ,
19
+ - enables _double-splat_ operator: `**decorated`,
20
+ - enumerating methods yield decorated items.
21
+
1
22
  ## [0.1.0] — 2024-10-13
2
23
 
3
24
  ### Added
4
25
 
5
26
  - `Magic::Decorator::Base` — a basic decorator class.
6
27
  - `Magic::Decoratable` to be included in decoratable classes.
7
- - `#decorate`,
8
- - `#decorate!`,
9
- - `#decorated`,
10
- - `#decorated?`.
28
+ - `#decorate`,
29
+ - `#decorate!`,
30
+ - `#decorated`,
31
+ - `#decorated?`.
data/README.md CHANGED
@@ -47,20 +47,55 @@ person.name # => "John Smith"
47
47
  This module adds three methods to decorate an object.
48
48
  Decorator class is being inferred automatically.
49
49
  When no decorator is found,
50
- * `#decorate` returns `nil`,
51
- * `#decorate!` raises `Magic::Lookup::Error`,
52
- * `#decorated` returns the original object.
50
+ - `#decorate` returns `nil`,
51
+ - `#decorate!` raises `Magic::Lookup::Error`,
52
+ - `#decorated` returns the original object.
53
53
 
54
54
  One can test for the object is actually decorated with `#decorated?`.
55
55
 
56
56
  ```ruby
57
- 'with no decorator for String'.decorated.decorated? # => false
58
- ['with a decorator for Array'].decorated.decorated? # => true
57
+ 'with no decorator for String'.decorated
58
+ .decorated? # => false
59
+ ['with a decorator for Array'].decorated
60
+ .decorated? # => true
59
61
  ```
60
62
 
61
- #### Magic
63
+ ## Magic
62
64
 
63
- `Decoratable` is mixed into `Object` by default. That means that effectively any object is `Decoratable`.
65
+ ### Decoratable scope
66
+
67
+ `Magic::Decoratable` is mixed into `Object` by default. It means that effectively any object is _magically decoratable_.
68
+
69
+ ### Decoration expansion
70
+
71
+ For almost any method called on a decorated object, both its result and `yield`ed arguments get decorated.
72
+
73
+ ```ruby
74
+ 'with no decorator for String'.decorated.chars
75
+ .decorated? # => false
76
+ ['with a decorator for Array'].decorated.map(&:chars).first.grep(/\S/).group_by(&:upcase).transform_values(&:size).sort_by(&:last).reverse.first(5).map(&:first)
77
+ .decorated? # => true
78
+ ```
79
+
80
+ #### Undecorated methods
81
+
82
+ Some methods aren’t meant to be decorated though:
83
+
84
+ - `deconstruct` & `deconstruct_keys` for _pattern matching_,
85
+ - _converting_ methods: those starting with `to_`,
86
+ - _system_ methods: those starting with `_`.
87
+
88
+ #### `undecorated` modifier
89
+
90
+ `Magic::Decorator::Base.undecorated` can be used to exclude methods from being decorated automagically.
91
+
92
+ ```ruby
93
+ class MyDecorator < Magic::Decorator::Base
94
+ undecorated %i[to_s inspect]
95
+ undecorated :raw_method
96
+ undecorated :m1, :m2
97
+ end
98
+ ```
64
99
 
65
100
  ### Decorator class inference
66
101
 
@@ -72,6 +107,33 @@ powered by [Magic Lookup](
72
107
  For example, `MyNamespace::MyModel.new.decorate` looks for `MyNamespace::MyModelDecorator` first.
73
108
  When missing, it further looks for decorators for its ancestor classes, up to `ObjectDecorator`.
74
109
 
110
+ ### Default decorators
111
+
112
+ #### `EnumerableDecorator`
113
+
114
+ It automagically decorates all its decoratable items.
115
+
116
+ ```ruby
117
+ [1, [2], { 3 => 4 }, '5'].decorated
118
+ .map &:decorated? # => [false, true, true, false]
119
+
120
+ { 1 => 2, [3] => [4] }.decorated.keys
121
+ .map &:decorated? # => [false, true]
122
+ { 1 => 2, [3] => [4] }.decorated.values
123
+ .map &:decorated? # => [false, true]
124
+
125
+ { 1 => 2, [3] => [4] }.decorated[1]
126
+ .decorated? # => false
127
+ { 1 => 2, [3] => [4] }.decorated[[3]]
128
+ .decorated? # => true
129
+ ```
130
+
131
+ ##### Side effects for decorated collections
132
+
133
+ - enables _splat_ operator: `*decorated` ,
134
+ - enables _double-splat_ operator: `**decorated`,
135
+ - enumerating methods yield decorated items.
136
+
75
137
  ## Development
76
138
 
77
139
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EnumerableDecorator < Magic::Decorator::Base
4
+ def to_a(...) = super.map &:decorated
5
+ def to_ary(...) = super.map &:decorated
6
+
7
+ def to_h(...) = super.to_h { |*h| h.map! &:decorated }
8
+ def to_hash(...) = super.to_h { |*h| h.map! &:decorated }
9
+ end
@@ -9,9 +9,51 @@ module Magic
9
9
 
10
10
  class << self
11
11
  def name_for(object_class) = "#{object_class}Decorator"
12
+
13
+ private
14
+
15
+ def undecorated method, *methods
16
+ return [ method, *methods ].map { undecorated _1 } if
17
+ methods.any?
18
+ return undecorated *method if
19
+ method.is_a? Array
20
+ raise TypeError, "#{method} is not a symbol nor a string" unless
21
+ method in Symbol | String
22
+
23
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
24
+ def #{method}(...) = __getobj__.#{method}(...)
25
+ RUBY
26
+ end
12
27
  end
13
28
 
14
29
  def decorated? = true
30
+
31
+ undecorated %i[
32
+ deconstruct
33
+ deconstruct_keys
34
+ ]
35
+
36
+ def method_missing(method, ...)
37
+ return super if method.start_with? 'to_' # converter
38
+ return super if method.start_with? '_' # system
39
+
40
+ if block_given?
41
+ super { |*args| yield *args.map(&:decorated) }
42
+ else
43
+ super
44
+ end.decorated
45
+ rescue NoMethodError => error
46
+ __raise__ error.class.new(
47
+ error.message.sub(self.class.name, __getobj__.class.name),
48
+ error.name,
49
+ error.args,
50
+ # error.private_call?, # FIXME: not implemented in TruffleRuby
51
+
52
+ receiver: __getobj__
53
+ ).tap {
54
+ _1.set_backtrace error.backtrace[2..] # FIXME: use `backtrace_locations` with Ruby 3.4+
55
+ }
56
+ end
15
57
  end
16
58
  end
17
59
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Magic
4
4
  module Decorator
5
- VERSION = '0.1.0'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
@@ -18,3 +18,5 @@ module Magic
18
18
  def name_for(...) = Base.name_for(...)
19
19
  end
20
20
  end
21
+
22
+ require 'enumerable_decorator'
@@ -0,0 +1,9 @@
1
+ class EnumerableDecorator[unchecked out Element]
2
+ include _Each[Element, void]
3
+ include Enumerable[Element]
4
+
5
+ def to_ary: () -> ::Array[Element]
6
+
7
+ def to_hash: () -> ::Hash[untyped, untyped]
8
+ | [T, U] () { (Element) -> [T, U] } -> ::Hash[T, U]
9
+ end
@@ -4,6 +4,12 @@ module Magic
4
4
  extend Lookup
5
5
 
6
6
  def decorated?: -> bool
7
+
8
+ private
9
+
10
+ def self.undecorated: (interned) -> Symbol
11
+ | (interned, interned, *interned) -> Array[Symbol]
12
+ | (Array[interned]) -> Array[Symbol]
7
13
  end
8
14
  end
9
15
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: magic-decorator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Senko
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2024-10-13 00:00:00.000000000 Z
10
+ date: 2024-10-17 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: magic-lookup
@@ -38,11 +38,13 @@ files:
38
38
  - LICENSE.txt
39
39
  - README.md
40
40
  - Rakefile
41
+ - lib/enumerable_decorator.rb
41
42
  - lib/magic/decoratable.rb
42
43
  - lib/magic/decorator.rb
43
44
  - lib/magic/decorator/authors.rb
44
45
  - lib/magic/decorator/base.rb
45
46
  - lib/magic/decorator/version.rb
47
+ - sig/enumerable_decorator.rbs
46
48
  - sig/gem.rbs
47
49
  - sig/magic/decoratable.rbs
48
50
  - sig/magic/decorator.rbs
@@ -53,7 +55,7 @@ licenses:
53
55
  metadata:
54
56
  homepage_uri: https://github.com/Alexander-Senko/magic-decorator
55
57
  source_code_uri: https://github.com/Alexander-Senko/magic-decorator
56
- changelog_uri: https://github.com/Alexander-Senko/magic-decorator/blob/v0.1.0/CHANGELOG.md
58
+ changelog_uri: https://github.com/Alexander-Senko/magic-decorator/blob/v0.2.0/CHANGELOG.md
57
59
  rdoc_options: []
58
60
  require_paths:
59
61
  - lib
@@ -61,7 +63,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
63
  requirements:
62
64
  - - "~>"
63
65
  - !ruby/object:Gem::Version
64
- version: '3.1'
66
+ version: '3.2'
65
67
  required_rubygems_version: !ruby/object:Gem::Requirement
66
68
  requirements:
67
69
  - - ">="