metaractor-sycamore 0.4.1

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.
data/devenv.lock ADDED
@@ -0,0 +1,210 @@
1
+ {
2
+ "nodes": {
3
+ "devenv": {
4
+ "locked": {
5
+ "dir": "src/modules",
6
+ "lastModified": 1686054274,
7
+ "narHash": "sha256-93aebyN7EMmeFFXisFIvp28UEbrozu79vd3pKPjvNR0=",
8
+ "owner": "cachix",
9
+ "repo": "devenv",
10
+ "rev": "c51a56bac8853c019241fe8d821c0a0d82422835",
11
+ "type": "github"
12
+ },
13
+ "original": {
14
+ "dir": "src/modules",
15
+ "owner": "cachix",
16
+ "repo": "devenv",
17
+ "type": "github"
18
+ }
19
+ },
20
+ "flake-compat": {
21
+ "flake": false,
22
+ "locked": {
23
+ "lastModified": 1673956053,
24
+ "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
25
+ "owner": "edolstra",
26
+ "repo": "flake-compat",
27
+ "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
28
+ "type": "github"
29
+ },
30
+ "original": {
31
+ "owner": "edolstra",
32
+ "repo": "flake-compat",
33
+ "type": "github"
34
+ }
35
+ },
36
+ "flake-compat_2": {
37
+ "flake": false,
38
+ "locked": {
39
+ "lastModified": 1673956053,
40
+ "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
41
+ "owner": "edolstra",
42
+ "repo": "flake-compat",
43
+ "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
44
+ "type": "github"
45
+ },
46
+ "original": {
47
+ "owner": "edolstra",
48
+ "repo": "flake-compat",
49
+ "type": "github"
50
+ }
51
+ },
52
+ "flake-utils": {
53
+ "locked": {
54
+ "lastModified": 1667395993,
55
+ "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
56
+ "owner": "numtide",
57
+ "repo": "flake-utils",
58
+ "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
59
+ "type": "github"
60
+ },
61
+ "original": {
62
+ "owner": "numtide",
63
+ "repo": "flake-utils",
64
+ "type": "github"
65
+ }
66
+ },
67
+ "flake-utils_2": {
68
+ "inputs": {
69
+ "systems": "systems"
70
+ },
71
+ "locked": {
72
+ "lastModified": 1685518550,
73
+ "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
74
+ "owner": "numtide",
75
+ "repo": "flake-utils",
76
+ "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
77
+ "type": "github"
78
+ },
79
+ "original": {
80
+ "owner": "numtide",
81
+ "repo": "flake-utils",
82
+ "type": "github"
83
+ }
84
+ },
85
+ "gitignore": {
86
+ "inputs": {
87
+ "nixpkgs": [
88
+ "pre-commit-hooks",
89
+ "nixpkgs"
90
+ ]
91
+ },
92
+ "locked": {
93
+ "lastModified": 1660459072,
94
+ "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
95
+ "owner": "hercules-ci",
96
+ "repo": "gitignore.nix",
97
+ "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
98
+ "type": "github"
99
+ },
100
+ "original": {
101
+ "owner": "hercules-ci",
102
+ "repo": "gitignore.nix",
103
+ "type": "github"
104
+ }
105
+ },
106
+ "nixpkgs": {
107
+ "locked": {
108
+ "lastModified": 1685938391,
109
+ "narHash": "sha256-96Jw6TbWDLSopt5jqCW8w1Fc1cjQyZlhfBnJ3OZGpME=",
110
+ "owner": "NixOS",
111
+ "repo": "nixpkgs",
112
+ "rev": "31cd1b4afbaf0b1e81272ee9c31d1ab606503aed",
113
+ "type": "github"
114
+ },
115
+ "original": {
116
+ "owner": "NixOS",
117
+ "ref": "nixpkgs-unstable",
118
+ "repo": "nixpkgs",
119
+ "type": "github"
120
+ }
121
+ },
122
+ "nixpkgs-ruby": {
123
+ "inputs": {
124
+ "flake-compat": "flake-compat",
125
+ "flake-utils": "flake-utils",
126
+ "nixpkgs": [
127
+ "nixpkgs"
128
+ ]
129
+ },
130
+ "locked": {
131
+ "lastModified": 1685159223,
132
+ "narHash": "sha256-rwHYapZEvxDJ8dw3hMGY/AE9c6qls9CifAUvObnx7e0=",
133
+ "owner": "bobvanderlinden",
134
+ "repo": "nixpkgs-ruby",
135
+ "rev": "dfaf58567eda18bad5340a9432e601d96c497943",
136
+ "type": "github"
137
+ },
138
+ "original": {
139
+ "owner": "bobvanderlinden",
140
+ "repo": "nixpkgs-ruby",
141
+ "type": "github"
142
+ }
143
+ },
144
+ "nixpkgs-stable": {
145
+ "locked": {
146
+ "lastModified": 1685801374,
147
+ "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=",
148
+ "owner": "NixOS",
149
+ "repo": "nixpkgs",
150
+ "rev": "c37ca420157f4abc31e26f436c1145f8951ff373",
151
+ "type": "github"
152
+ },
153
+ "original": {
154
+ "owner": "NixOS",
155
+ "ref": "nixos-23.05",
156
+ "repo": "nixpkgs",
157
+ "type": "github"
158
+ }
159
+ },
160
+ "pre-commit-hooks": {
161
+ "inputs": {
162
+ "flake-compat": "flake-compat_2",
163
+ "flake-utils": "flake-utils_2",
164
+ "gitignore": "gitignore",
165
+ "nixpkgs": [
166
+ "nixpkgs"
167
+ ],
168
+ "nixpkgs-stable": "nixpkgs-stable"
169
+ },
170
+ "locked": {
171
+ "lastModified": 1686050334,
172
+ "narHash": "sha256-R0mczWjDzBpIvM3XXhO908X5e2CQqjyh/gFbwZk/7/Q=",
173
+ "owner": "cachix",
174
+ "repo": "pre-commit-hooks.nix",
175
+ "rev": "6881eb2ae5d8a3516e34714e7a90d9d95914c4dc",
176
+ "type": "github"
177
+ },
178
+ "original": {
179
+ "owner": "cachix",
180
+ "repo": "pre-commit-hooks.nix",
181
+ "type": "github"
182
+ }
183
+ },
184
+ "root": {
185
+ "inputs": {
186
+ "devenv": "devenv",
187
+ "nixpkgs": "nixpkgs",
188
+ "nixpkgs-ruby": "nixpkgs-ruby",
189
+ "pre-commit-hooks": "pre-commit-hooks"
190
+ }
191
+ },
192
+ "systems": {
193
+ "locked": {
194
+ "lastModified": 1681028828,
195
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
196
+ "owner": "nix-systems",
197
+ "repo": "default",
198
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
199
+ "type": "github"
200
+ },
201
+ "original": {
202
+ "owner": "nix-systems",
203
+ "repo": "default",
204
+ "type": "github"
205
+ }
206
+ }
207
+ },
208
+ "root": "root",
209
+ "version": 7
210
+ }
data/devenv.nix ADDED
@@ -0,0 +1,25 @@
1
+ { pkgs, ... }:
2
+
3
+ {
4
+ # https://devenv.sh/packages/
5
+ packages = with pkgs; [
6
+ git
7
+ ];
8
+
9
+ # https://devenv.sh/languages/
10
+ languages.ruby.enable = true;
11
+ # Uses bobvanderlinden/nixpkgs-ruby to supply any version of ruby
12
+ languages.ruby.versionFile = ./.ruby-version;
13
+
14
+ enterShell = ''
15
+ export BUNDLE_BIN="$DEVENV_ROOT/.devenv/bin"
16
+ export PATH="$DEVENV_PROFILE/bin:$DEVENV_ROOT/bin:$BUNDLE_BIN:$PATH"
17
+ export BOOTSNAP_CACHE_DIR="$DEVENV_ROOT/.devenv/state"
18
+ '';
19
+
20
+ # The unix socket path can't be "too long".
21
+ # Make sure it's short for when we need it.
22
+ env.RUBY_DEBUG_SOCK_DIR = "/tmp/";
23
+
24
+ # See full reference at https://devenv.sh/reference/options/
25
+ }
data/devenv.yaml ADDED
@@ -0,0 +1,8 @@
1
+ inputs:
2
+ nixpkgs:
3
+ url: github:NixOS/nixpkgs/nixpkgs-unstable
4
+ nixpkgs-ruby:
5
+ url: github:bobvanderlinden/nixpkgs-ruby
6
+ inputs:
7
+ nixpkgs:
8
+ follows: nixpkgs
@@ -0,0 +1 @@
1
+ require 'sycamore'
@@ -0,0 +1,179 @@
1
+ require 'delegate'
2
+
3
+ module Sycamore
4
+
5
+ ##
6
+ # An Absence object represents the absence of a specific child {Sycamore::Tree}.
7
+ #
8
+ # +Absence+ instances get created when accessing non-existent children of a
9
+ # Tree with {Tree#child_of} or {Tree#child_at}.
10
+ # It is not intended to be instantiated by the user.
11
+ #
12
+ # An +Absence+ object can be used like a normal {Sycamore::Tree}.
13
+ # {Tree::QUERY_METHODS Query} and {Tree::DESTRUCTIVE_COMMAND_METHODS pure destructive command method}
14
+ # calls get delegated to {Sycamore::Nothing}, i.e. will behave like an empty Tree.
15
+ # On every other {Tree::COMMAND_METHODS command} calls, the +Absence+ object
16
+ # gets resolved, which means the missing tree will be created, added to the
17
+ # parent tree and the method call gets delegated to the now existing tree.
18
+ # After the +Absence+ object is resolved all subsequent method calls are
19
+ # delegated to the created tree.
20
+ # The type of tree eventually created is determined by the {Tree#new_child}
21
+ # implementation of the parent tree and the parent node.
22
+ #
23
+ class Absence < Delegator
24
+
25
+ ##
26
+ # @api private
27
+ #
28
+ def initialize(parent_tree, parent_node)
29
+ @parent_tree, @parent_node = parent_tree, parent_node
30
+ end
31
+
32
+ class << self
33
+ alias at new
34
+ end
35
+
36
+ ########################################################################
37
+ # presence creation
38
+ ########################################################################
39
+
40
+ ##
41
+ # The tree object to which all method calls are delegated.
42
+ #
43
+ # @api private
44
+ #
45
+ def presence
46
+ @tree or Nothing
47
+ end
48
+
49
+ alias __getobj__ presence
50
+
51
+ ##
52
+ # @api private
53
+ #
54
+ def create
55
+ @parent_tree = @parent_tree.add_node_with_empty_child(@parent_node)
56
+ @tree = @parent_tree[@parent_node]
57
+ end
58
+
59
+ ########################################################################
60
+ # Absence and Nothing predicates
61
+ ########################################################################
62
+
63
+ ##
64
+ # (see Tree#absent?)
65
+ #
66
+ def absent?
67
+ @tree.nil?
68
+ end
69
+
70
+ ##
71
+ # (see Tree#nothing?)
72
+ #
73
+ def nothing?
74
+ false
75
+ end
76
+
77
+ ########################################################################
78
+ # Element access
79
+ ########################################################################
80
+
81
+ #####################
82
+ # query methods #
83
+ #####################
84
+
85
+ def child_of(node)
86
+ if absent?
87
+ raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
88
+
89
+ Absence.at(self, node)
90
+ else
91
+ presence.child_of(node)
92
+ end
93
+ end
94
+
95
+ def child_at(*path)
96
+ if absent?
97
+ # TODO: This is duplication of Tree#child_at! How can we remove it, without introducing a module for this single method or inherit from Tree?
98
+ case path.length
99
+ when 0 then raise ArgumentError, 'wrong number of arguments (given 0, expected 1+)'
100
+ when 1 then child_of(*path)
101
+ else child_of(path[0]).child_at(*path[1..-1])
102
+ end
103
+ else
104
+ presence.child_at(*path)
105
+ end
106
+ end
107
+
108
+ alias [] child_at
109
+ alias dig child_at
110
+
111
+ ##
112
+ # A developer-friendly string representation of the absent tree.
113
+ #
114
+ # @return [String]
115
+ #
116
+ def inspect
117
+ "#{absent? ? 'absent' : 'present'} child of node #{@parent_node.inspect} in #{@parent_tree.inspect}"
118
+ end
119
+
120
+ ##
121
+ # Duplicates the resolved tree or raises an error, when unresolved.
122
+ #
123
+ # @return [Tree]
124
+ #
125
+ # @raise [TypeError] when this {Absence} is not resolved yet
126
+ #
127
+ def dup
128
+ presence.dup
129
+ end
130
+
131
+ ##
132
+ # Clones the resolved tree or raises an error, when unresolved.
133
+ #
134
+ # @return [Tree]
135
+ #
136
+ # @raise [TypeError] when this {Absence} is not resolved yet
137
+ #
138
+ def clone
139
+ presence.clone
140
+ end
141
+
142
+ ##
143
+ # Checks if the absent tree is frozen.
144
+ #
145
+ # @return [Boolean]
146
+ #
147
+ def frozen?
148
+ if absent?
149
+ false
150
+ else
151
+ presence.frozen?
152
+ end
153
+ end
154
+
155
+ #####################
156
+ # command methods #
157
+ #####################
158
+
159
+ # TODO: YARD should be informed about this method definitions.
160
+ Tree.command_methods.each do |command_method|
161
+ if Tree.pure_destructive_command_methods.include?(command_method)
162
+ define_method command_method do |*args|
163
+ if absent?
164
+ self
165
+ else
166
+ presence.send(command_method, *args) # TODO: How can we hand over a possible block? With eval etc.?
167
+ end
168
+ end
169
+ else
170
+ # TODO: This method should be atomic.
171
+ define_method command_method do |*args|
172
+ create if absent?
173
+ presence.send(command_method, *args) # TODO: How can we hand over a possible block? With eval etc.?
174
+ end
175
+ end
176
+ end
177
+
178
+ end
179
+ end
@@ -0,0 +1,16 @@
1
+ module Sycamore
2
+ # raised when a value is not a valid node
3
+ class InvalidNode < ArgumentError ; end
4
+
5
+ # raised when trying to call a additive command method of the {Nothing} tree
6
+ class NothingMutation < StandardError ; end
7
+
8
+ # raised when calling {Tree#node} or {Tree#node!} on a Tree with multiple nodes
9
+ class NonUniqueNodeSet < StandardError ; end
10
+
11
+ # raised when calling {Tree#node!} on a Tree without nodes
12
+ class EmptyNodeSet < StandardError ; end
13
+
14
+ # raised when trying to fetch the child of a leaf
15
+ class ChildError < KeyError ; end
16
+ end
@@ -0,0 +1,4 @@
1
+ require 'sycamore/nothing'
2
+
3
+ # optional global shortcut constant for {Sycamore::Nothing}
4
+ Nothing = Sycamore::Nothing unless defined? Nothing
@@ -0,0 +1,7 @@
1
+ require 'sycamore'
2
+
3
+ # optional global shortcut constant for Sycamore::Path
4
+ Path = Sycamore::Path
5
+
6
+ # optional global shortcut constant for Sycamore::Path
7
+ TreePath = Sycamore::Path
@@ -0,0 +1,4 @@
1
+ require 'sycamore'
2
+
3
+ # optional global shortcut constant for Sycamore::Tree
4
+ Tree = Sycamore::Tree
@@ -0,0 +1,2 @@
1
+ require 'sycamore/extension/tree'
2
+ require 'sycamore/extension/nothing'
@@ -0,0 +1,157 @@
1
+ require 'singleton'
2
+
3
+ module Sycamore
4
+
5
+ ##
6
+ # The Nothing Tree singleton class.
7
+ #
8
+ # The Nothing Tree is an empty Sycamore Tree, and means "there are no nodes".
9
+ #
10
+ # It is immutable:
11
+ # - {Tree::QUERY_METHODS Query method} calls will behave like a normal, empty Tree.
12
+ # - {Tree::DESTRUCTIVE_COMMAND_METHODS Pure destructive command} calls, will be ignored, i.e. being no-ops.
13
+ # - But all other {Tree::COMMAND_METHODS command} calls, will raise a {NothingMutation}.
14
+ #
15
+ # It is the only Tree object that will return +true+ on a {#nothing?} call.
16
+ # But like {Absence}, it will return +true+ on {#absent?} and +false+ on {#existent?}.
17
+ #
18
+ class NothingTree < Tree
19
+ include Singleton
20
+
21
+ ########################################################################
22
+ # Absence and Nothing predicates
23
+ ########################################################################
24
+
25
+ ##
26
+ # (see Tree#nothing?)
27
+ #
28
+ def nothing?
29
+ true
30
+ end
31
+
32
+ ##
33
+ # (see Tree#absent?)
34
+ #
35
+ def absent?
36
+ true
37
+ end
38
+
39
+ ########################################################################
40
+ # CQS element access
41
+ ########################################################################
42
+
43
+ # TODO: YARD should be informed about this method definitions.
44
+ command_methods.each do |command_method|
45
+ define_method command_method do |*args|
46
+ raise NothingMutation, 'attempt to change the Nothing tree'
47
+ end
48
+ end
49
+
50
+ # TODO: YARD should be informed about this method definitions.
51
+ pure_destructive_command_methods.each do |command_method|
52
+ define_method(command_method) { |*args| self }
53
+ end
54
+
55
+ def child_of(node)
56
+ self
57
+ end
58
+
59
+ def to_native_object(sleaf_child_as: nil, **args)
60
+ sleaf_child_as
61
+ end
62
+
63
+ ##
64
+ # A string representation of the Nothing tree.
65
+ #
66
+ # @return [String]
67
+ #
68
+ def to_s
69
+ 'Tree[Nothing]'
70
+ end
71
+
72
+ ##
73
+ # A developer-friendly string representation of the Nothing tree.
74
+ #
75
+ # @return [String]
76
+ #
77
+ def inspect
78
+ '#<Sycamore::Nothing>'
79
+ end
80
+
81
+ def freeze
82
+ super
83
+ end
84
+
85
+ ########################################################################
86
+ # Equality
87
+ ########################################################################
88
+
89
+ ##
90
+ # Checks if the given object is an empty tree.
91
+ #
92
+ # @return [Boolean]
93
+ #
94
+ def ==(other)
95
+ (other.is_a?(Tree) or other.is_a?(Absence)) and other.empty?
96
+ end
97
+
98
+
99
+ ########################################################################
100
+ # Falsiness
101
+ #
102
+ # Sadly, in Ruby we can't do that match to reach this goal.
103
+ #
104
+ # see http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/
105
+ ########################################################################
106
+
107
+ ##
108
+ # Try to emulate a falsey value, by negating to +true+.
109
+ #
110
+ # @return [Boolean] +true+
111
+ #
112
+ # @see http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/
113
+ #
114
+ def !
115
+ true
116
+ end
117
+
118
+ # def nil?
119
+ # true
120
+ # end
121
+
122
+
123
+ ########################################################################
124
+ # Some helpers
125
+ #
126
+ # Ideally these would be implemented with Refinements, but since they
127
+ # aren't available anywhere (I'm looking at you, JRuby), we have to be
128
+ # content with this.
129
+ #
130
+ ########################################################################
131
+
132
+ def like?(object)
133
+ object.nil? or object.equal? self
134
+ end
135
+
136
+ ##
137
+ # @api private
138
+ class NestedStringPresentation
139
+ include Singleton
140
+
141
+ def inspect
142
+ 'n/a'
143
+ end
144
+ end
145
+
146
+ ##
147
+ # @api private
148
+ NestedString = NestedStringPresentation.instance.freeze
149
+
150
+ end
151
+
152
+ ############################################################################
153
+ # The Nothing Tree Singleton object
154
+ #
155
+ Nothing = NothingTree.instance.freeze
156
+
157
+ end