metaractor-sycamore 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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