metaractor-sycamore 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +24 -0
- data/.envrc +3 -0
- data/.github/workflows/specs.yml +13 -0
- data/.gitignore +16 -0
- data/.rspec +6 -0
- data/.ruby-version +1 -0
- data/.yardopts +13 -0
- data/AUTHORS +2 -0
- data/CHANGELOG.md +88 -0
- data/CONTRIBUTING.md +5 -0
- data/CREDITS +0 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +22 -0
- data/README.md +571 -0
- data/Rakefile +36 -0
- data/VERSION +1 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/devenv.lock +210 -0
- data/devenv.nix +25 -0
- data/devenv.yaml +8 -0
- data/lib/outstand_sycamore.rb +1 -0
- data/lib/sycamore/absence.rb +179 -0
- data/lib/sycamore/exceptions.rb +16 -0
- data/lib/sycamore/extension/nothing.rb +4 -0
- data/lib/sycamore/extension/path.rb +7 -0
- data/lib/sycamore/extension/tree.rb +4 -0
- data/lib/sycamore/extension.rb +2 -0
- data/lib/sycamore/nothing.rb +157 -0
- data/lib/sycamore/path.rb +266 -0
- data/lib/sycamore/path_root.rb +43 -0
- data/lib/sycamore/stree.rb +4 -0
- data/lib/sycamore/tree.rb +1469 -0
- data/lib/sycamore/version.rb +28 -0
- data/lib/sycamore.rb +13 -0
- data/support/doctest_helper.rb +2 -0
- data/support/travis.sh +6 -0
- data/sycamore.gemspec +29 -0
- metadata +152 -0
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 @@
|
|
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,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
|