nodus 0.3.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.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +38 -0
- data/LICENSE.txt +20 -0
- data/OPERUM.md +8 -0
- data/README.md +383 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/dia.rb +29 -0
- data/doc/desc.md +191 -0
- data/doc/example.node +89 -0
- data/doc/nodes.rb +77 -0
- data/doc/pipe.svg +97 -0
- data/doc/pipe.txt +4 -0
- data/doc/pipe2.dot +49 -0
- data/doc/pipe2.svg +163 -0
- data/lib/VERSION +1 -0
- data/lib/extensions.rb +162 -0
- data/lib/flexhash.rb +175 -0
- data/lib/nodus.rb +77 -0
- data/lib/nodus/nodes.rb +160 -0
- data/lib/nodus/stream.rb +12 -0
- data/lib/nodus/token.rb +31 -0
- data/lib/nodus/version.rb +6 -0
- data/lib/proplist.rb +142 -0
- data/nodus.gemspec +106 -0
- data/spec.md +60 -0
- data/test/core/test_flexhash.rb +87 -0
- data/test/core/test_generator.rb +27 -0
- data/test/core/test_node.rb +103 -0
- data/test/core/test_proplist.rb +153 -0
- data/test/helper.rb +107 -0
- metadata +188 -0
data/lib/proplist.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
|
2
|
+
# TODO: REFACTOR, CLEANUP, and SIMPLIFY!
|
3
|
+
|
4
|
+
# Properties of properties (such as required, default, type, etc.)
|
5
|
+
#
|
6
|
+
# TODO: change this into a mixin module? Make it more like a simple add-on to openstruct? (and put in its own file)
|
7
|
+
|
8
|
+
class PropSet
|
9
|
+
class_attr_inheritable :inverses, {}
|
10
|
+
class_attr_inheritable :defaults, {} # Don't confuse this with the property's default- it's the property's property's default (e.g., `required` defaults to false, or `default` defaults to nil)
|
11
|
+
|
12
|
+
class << self
|
13
|
+
protected def inverse(kvpairs) inverses.merge!(kvpairs) end # TODO: verify that keys aren't values and that there are no duplicate values
|
14
|
+
protected def default(kvpairs) defaults.merge!(kvpairs) end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(*opts)
|
18
|
+
@data = {}
|
19
|
+
merge_opts(*opts)
|
20
|
+
end
|
21
|
+
|
22
|
+
def inspect() "#<#{self.class.name} #{@data.inspect}>" end
|
23
|
+
def to_hash() @data end
|
24
|
+
|
25
|
+
def merge_opts(*opts)
|
26
|
+
opts = [opts].flatten.reduce({}){|h,o| Hash === o ? h.merge(o) : h.merge({o.to_sym => true})}
|
27
|
+
opts.each do |k, v|
|
28
|
+
k_str = k.to_s
|
29
|
+
k_sym = k.to_sym
|
30
|
+
if k_str.starts_with?('no_')
|
31
|
+
if v = true
|
32
|
+
k_to_remove = k_str[3..-1].to_sym
|
33
|
+
@data.delete(k_to_remove)
|
34
|
+
@data.delete(inverses[k_to_remove])
|
35
|
+
else
|
36
|
+
error ArgumentError, "Parameter aspects that start with 'no_' are for unsetting that aspect of the property. For example, `no_default: true`- Always expecting 'true' as the value."
|
37
|
+
end
|
38
|
+
else
|
39
|
+
if inverses[k_sym]
|
40
|
+
error ArgumentError, "Expected true or false value for #{k_sym} aspect of property #{@name}" unless v == !!v
|
41
|
+
v = !v
|
42
|
+
k_sym = inverses[k_sym]
|
43
|
+
end
|
44
|
+
@data[k_sym] = v
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def method_missing(m, *a, &b)
|
50
|
+
# TODO: check @data for key before checking it for respond_to to solve future problems like this :default hack
|
51
|
+
return @data.send(m, *a, &b) if @data.respond_to?(m) && ![:default,:default=].include?(m.to_sym)
|
52
|
+
case m.to_s
|
53
|
+
when /^(.+)=$/ then merge_opts($1 => (a.size == 1 ? a[0] : a))
|
54
|
+
when /^has_(.+)\?$/ then @data.has_key?($1.to_sym) || @data.has_key?(inverses[$1.to_sym])
|
55
|
+
when /^(.+)\?$/ then !!val_for($1)
|
56
|
+
else val_for(m)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def val_for(key)
|
61
|
+
key = key.to_sym
|
62
|
+
rev = inverses.has_key?(key)
|
63
|
+
key = inverses[key] if rev
|
64
|
+
val = nil
|
65
|
+
if @data.has_key?(key) || defaults.has_key?(key)
|
66
|
+
val = @data.has_key?(key) ? @data[key] : defaults[key]
|
67
|
+
val = !val if rev
|
68
|
+
end
|
69
|
+
val
|
70
|
+
end
|
71
|
+
|
72
|
+
# When the property is given a value specify (via overriding this method) anything special that needs to happen to the
|
73
|
+
# PropSet instance.
|
74
|
+
def realize(val) self.value = val end
|
75
|
+
def realized?() self.has_value? end
|
76
|
+
def realized
|
77
|
+
return self.value if self.has_value?
|
78
|
+
return self.default if self.has_default?
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def dup() Marshal.load(Marshal.dump(self)) end
|
83
|
+
end
|
84
|
+
|
85
|
+
class PropList
|
86
|
+
def initialize(propclass=PropSet)
|
87
|
+
@propclass = propclass
|
88
|
+
@data = {}
|
89
|
+
end
|
90
|
+
|
91
|
+
# TODO: have the node say "parameter[]" when it wants a singular results with possibly a warning or even exception
|
92
|
+
# when more than one matches, vs "parameters[]" which always gives an array, even if only one or zero matches...
|
93
|
+
|
94
|
+
def [](kvs)
|
95
|
+
kvs = {name: kvs} unless Hash === kvs
|
96
|
+
|
97
|
+
res = @data.values
|
98
|
+
kvs.each{|k,v| res = res.select{|prop| prop.send(k) == v}}
|
99
|
+
|
100
|
+
case res.size
|
101
|
+
when 0 then nil
|
102
|
+
when 1 then res[0]
|
103
|
+
else res end
|
104
|
+
end
|
105
|
+
|
106
|
+
def dup() Marshal.load(Marshal.dump(self)) end
|
107
|
+
|
108
|
+
def realize(name, value) add(name).tap{|pset| pset.realize(value)} end
|
109
|
+
alias_method :[]=, :realize
|
110
|
+
|
111
|
+
# TODO: add-override separate from add-unique - the first used in inheritance, for example when currying parameters,
|
112
|
+
# and the latter used when composing many together that might normally have name conflicts.
|
113
|
+
|
114
|
+
def add_name_opts(name_and_opts) name = name_and_opts.shift; add(name, name_and_opts) end
|
115
|
+
alias_method :<<, :add_name_opts
|
116
|
+
|
117
|
+
def add(name, *opts)
|
118
|
+
name = name.to_sym
|
119
|
+
if @data[name] && opts.present? then @data[name].merge_opts(opts)
|
120
|
+
elsif !@data[name] then @data[name] = @propclass.new(opts, name: name) end
|
121
|
+
@data[name]
|
122
|
+
end
|
123
|
+
|
124
|
+
def respond_to?(m, inc_all=false)
|
125
|
+
@data.has_key?(m.to_sym) || @data.respond_to?(m, inc_all) || !!m[/has_.+\?/]
|
126
|
+
end
|
127
|
+
|
128
|
+
def method_missing(m, *a, &b)
|
129
|
+
return @data[m.to_sym] if @data.has_key?(m.to_sym)
|
130
|
+
return @data.send(m, *a, &b) if @data.respond_to?(m)
|
131
|
+
return @data.has_key?(m[4..-2].to_sym) if m[/has_.+\?/]
|
132
|
+
super
|
133
|
+
end
|
134
|
+
|
135
|
+
# TODO: method_missing revolves around `by_xxxx` - gives a hash indexed by the stated propset-value
|
136
|
+
# if no `by_` then it is assumed to be the index into ANY of the propset-values that has a symbol or string as
|
137
|
+
# the value. If the by_xxxx were chainable, it could be a method_missing equivalent of the [] search method...
|
138
|
+
# (probably not important)
|
139
|
+
|
140
|
+
def include?(k) super(k.to_sym) end
|
141
|
+
alias_method :includes?, :include?
|
142
|
+
end
|
data/nodus.gemspec
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: nodus 0.3.1 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "nodus"
|
9
|
+
s.version = "0.3.1"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.authors = ["Joseph Wecker"]
|
14
|
+
s.date = "2014-07-31"
|
15
|
+
s.description = "EXPERIMENTAL. A form of data-flow programming based loosely on Kahn Process Networks. Will allow for setting up operational components that can be pipelined together in a graph. Assumes all components (nodes) are 'online' algorithms with more or less steady-state resource utilization for continuous streams of data."
|
16
|
+
s.email = "joseph.wecker@exsig.com"
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.txt",
|
19
|
+
"README.md"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".ruby-gemset",
|
24
|
+
".ruby-version",
|
25
|
+
"Gemfile",
|
26
|
+
"LICENSE.txt",
|
27
|
+
"OPERUM.md",
|
28
|
+
"README.md",
|
29
|
+
"Rakefile",
|
30
|
+
"VERSION",
|
31
|
+
"dia.rb",
|
32
|
+
"doc/desc.md",
|
33
|
+
"doc/example.node",
|
34
|
+
"doc/nodes.rb",
|
35
|
+
"doc/pipe.svg",
|
36
|
+
"doc/pipe.txt",
|
37
|
+
"doc/pipe2.dot",
|
38
|
+
"doc/pipe2.svg",
|
39
|
+
"lib/VERSION",
|
40
|
+
"lib/extensions.rb",
|
41
|
+
"lib/flexhash.rb",
|
42
|
+
"lib/nodus.rb",
|
43
|
+
"lib/nodus/nodes.rb",
|
44
|
+
"lib/nodus/stream.rb",
|
45
|
+
"lib/nodus/token.rb",
|
46
|
+
"lib/nodus/version.rb",
|
47
|
+
"lib/proplist.rb",
|
48
|
+
"nodus.gemspec",
|
49
|
+
"spec.md",
|
50
|
+
"test/core/test_flexhash.rb",
|
51
|
+
"test/core/test_generator.rb",
|
52
|
+
"test/core/test_node.rb",
|
53
|
+
"test/core/test_proplist.rb",
|
54
|
+
"test/helper.rb"
|
55
|
+
]
|
56
|
+
s.homepage = "http://github.com/exsig/nodus"
|
57
|
+
s.licenses = ["MIT"]
|
58
|
+
s.rubygems_version = "2.2.2"
|
59
|
+
s.summary = "Something between a Kahn Process Network and Algorithmic Skeleton for parallel pipelining and signal processing"
|
60
|
+
|
61
|
+
if s.respond_to? :specification_version then
|
62
|
+
s.specification_version = 4
|
63
|
+
|
64
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
65
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 4.1.0"])
|
66
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
67
|
+
s.add_development_dependency(%q<git>, [">= 0"])
|
68
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
69
|
+
s.add_development_dependency(%q<brice>, [">= 0"])
|
70
|
+
s.add_development_dependency(%q<ansi>, [">= 0"])
|
71
|
+
s.add_development_dependency(%q<rdoc>, [">= 0"])
|
72
|
+
s.add_development_dependency(%q<ffaker>, [">= 0"])
|
73
|
+
s.add_development_dependency(%q<randexp>, [">= 0"])
|
74
|
+
s.add_development_dependency(%q<minitest>, [">= 5.3"])
|
75
|
+
s.add_development_dependency(%q<minitest-reporters>, [">= 1.0.1"])
|
76
|
+
s.add_development_dependency(%q<simplecov>, ["~> 0.7.1"])
|
77
|
+
else
|
78
|
+
s.add_dependency(%q<activesupport>, [">= 4.1.0"])
|
79
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
80
|
+
s.add_dependency(%q<git>, [">= 0"])
|
81
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
82
|
+
s.add_dependency(%q<brice>, [">= 0"])
|
83
|
+
s.add_dependency(%q<ansi>, [">= 0"])
|
84
|
+
s.add_dependency(%q<rdoc>, [">= 0"])
|
85
|
+
s.add_dependency(%q<ffaker>, [">= 0"])
|
86
|
+
s.add_dependency(%q<randexp>, [">= 0"])
|
87
|
+
s.add_dependency(%q<minitest>, [">= 5.3"])
|
88
|
+
s.add_dependency(%q<minitest-reporters>, [">= 1.0.1"])
|
89
|
+
s.add_dependency(%q<simplecov>, ["~> 0.7.1"])
|
90
|
+
end
|
91
|
+
else
|
92
|
+
s.add_dependency(%q<activesupport>, [">= 4.1.0"])
|
93
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
94
|
+
s.add_dependency(%q<git>, [">= 0"])
|
95
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
96
|
+
s.add_dependency(%q<brice>, [">= 0"])
|
97
|
+
s.add_dependency(%q<ansi>, [">= 0"])
|
98
|
+
s.add_dependency(%q<rdoc>, [">= 0"])
|
99
|
+
s.add_dependency(%q<ffaker>, [">= 0"])
|
100
|
+
s.add_dependency(%q<randexp>, [">= 0"])
|
101
|
+
s.add_dependency(%q<minitest>, [">= 5.3"])
|
102
|
+
s.add_dependency(%q<minitest-reporters>, [">= 1.0.1"])
|
103
|
+
s.add_dependency(%q<simplecov>, ["~> 0.7.1"])
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
data/spec.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
* name (original)
|
4
|
+
* desc (description name in context of a group, or name by which it is thereafter registered as)
|
5
|
+
* parameterization
|
6
|
+
* number of input streams
|
7
|
+
* number of output streams
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
Node[node<n,n>] => node<n,n> # (ident)
|
12
|
+
Node[desc, node<n,n>] => node<n,n> # duplicate, but with a new name
|
13
|
+
Node[desc, object] => node<0,1> # makes generator node out of enumerator (each)
|
14
|
+
Node[desc, name] => node<n,n> # aliases a node
|
15
|
+
|
16
|
+
Node[desc, lambda_or_proc] => node<0..1,1> # arity on lambda simply unpacks array with arity check
|
17
|
+
Node[desc]{ ... } => node<0..1,1> # arity on block works just like enumerators etc.
|
18
|
+
|
19
|
+
Sink[desc]{ ... } => node<1,0> # block must have arity of 1 or more- first arg is always state. must pass updated state as result of the block run
|
20
|
+
# OR just allow some specialized @state object (initialized somehow?)
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
## Generator Nodes with single stream output
|
26
|
+
|
27
|
+
|
28
|
+
# Implied
|
29
|
+
def registered_name(*params)
|
30
|
+
Nodus.lookup(registered_name, *params)
|
31
|
+
end
|
32
|
+
|
33
|
+
registered_name(*params).as(contextual_name) # .as method simply appends to the name-chain (where the last member gets used by default) and returns the resulting node
|
34
|
+
|
35
|
+
# ?? necessary anymore?
|
36
|
+
nodelib(registered_name)
|
37
|
+
nodelib(registered_name, *parameters)
|
38
|
+
nodelib(registered_name, as: contextual_name, *parameters)
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
node(name){...} # warn on conflict with registered-name? implicit looping?
|
43
|
+
node(name, initial_state){|state| ...} # ditto
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
- name of registered node for lookup
|
48
|
+
- name for current context (already good default for anything looked-up, very important for lambdas etc.)
|
49
|
+
- initial-state/parameters
|
50
|
+
- kernel (as proc, block, lambda, class, or object- not very relevant for registered node lookups)
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
register node (?? necessary?)
|
55
|
+
* `node.register_as(:name)` or `node.register_as([:chain,:of,:names])`
|
56
|
+
* looks in `LIB_NODUS_NODE_PATHS` paths for modules/classes and maps to names
|
57
|
+
|
58
|
+
look-up & parameterize registered node:
|
59
|
+
|
60
|
+
look-up, parameterize, and rename registered node:
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require_relative '../helper.rb'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
describe FlexHash do
|
5
|
+
it 'can be initialized like a hash' do
|
6
|
+
h = FlexHash.new(name: 'hello', blah: 'there!')
|
7
|
+
h.must_be_instance_of FlexHash
|
8
|
+
h.size.must_equal 2
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'can be be initialized with an array' do
|
12
|
+
h = FlexHash.new([:one, 1, :two, 2])
|
13
|
+
h.must_be_instance_of FlexHash
|
14
|
+
h.size.must_equal 2
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'can be initialized with an array shorthand' do
|
18
|
+
h = FlexHash[:one, 1, :two, 2]
|
19
|
+
h.must_be_instance_of FlexHash
|
20
|
+
h.size.must_equal 2
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'can be initialized with a non-flat array shorthand' do
|
24
|
+
h = FlexHash[[:one, 1], [:two, 2]]
|
25
|
+
h.must_be_instance_of FlexHash
|
26
|
+
h.size.must_equal 2
|
27
|
+
end
|
28
|
+
|
29
|
+
subject { FlexHash.new(a: 100, b: 101, c: 102, d: 103) }
|
30
|
+
|
31
|
+
it 'can be accessed with methods' do
|
32
|
+
subject.a.must_equal 100
|
33
|
+
subject.d.must_equal 103
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'can be accessed with symbols' do
|
37
|
+
subject[:a].must_equal 100
|
38
|
+
subject[:b].must_equal 101
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'can be accessed with strings' do
|
42
|
+
subject['a'].must_equal 100
|
43
|
+
subject['c'].must_equal 102
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'accumulates more methods' do
|
47
|
+
subject[:hmmmmm] = 500
|
48
|
+
subject[:a].must_equal 100
|
49
|
+
subject[:hmmmmm].must_equal 500
|
50
|
+
subject.hmmmmm.must_equal 500
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'allows modifying of original values' do
|
54
|
+
subject[:a] = 200
|
55
|
+
subject[:a].must_equal 200
|
56
|
+
subject[:d].must_equal 103
|
57
|
+
subject.a.must_equal 200
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'allows position-based access of values' do
|
61
|
+
subject[0].must_equal 100
|
62
|
+
subject[3].must_equal 103
|
63
|
+
subject[4].must_equal nil
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'acts more like a hash than OpenStruct tends to' do
|
67
|
+
subject.shift.must_equal [:a, 100]
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
describe FlexArray do
|
73
|
+
it 'seems to work' do
|
74
|
+
a = FlexArray.new
|
75
|
+
a << OpenStruct.new(name: 'howdy')
|
76
|
+
a[1] = OpenStruct.new(name: :duty)
|
77
|
+
a << OpenStruct.new(name: 123)
|
78
|
+
|
79
|
+
a[0].name.must_equal 'howdy'
|
80
|
+
a['howdy'].name.must_equal 'howdy'
|
81
|
+
a.howdy.name.must_equal 'howdy'
|
82
|
+
a[/d/].map{|os| os.name.to_s}.sort.must_equal ['duty', 'howdy']
|
83
|
+
a[2].must_be_kind_of OpenStruct
|
84
|
+
end
|
85
|
+
|
86
|
+
# TODO: test for real
|
87
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative '../helper.rb'
|
2
|
+
include Nodus::Nodes
|
3
|
+
|
4
|
+
class GenClass; def each() loop{yield(rand)} end end
|
5
|
+
|
6
|
+
class GenClassWithRange
|
7
|
+
def initialize(range)
|
8
|
+
@range = range
|
9
|
+
end
|
10
|
+
def each() loop{yield(rand(@range))} end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Nodus::Nodes::Generator do
|
14
|
+
it 'can be created from a class with an "each" method' do
|
15
|
+
g = Generator[:mygen, GenClass]
|
16
|
+
g.must_be_a_node
|
17
|
+
g.title.must_equal :mygen
|
18
|
+
g.kernel.must_equal GenClass
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'infers parameters from the class initialization' do
|
22
|
+
g = Generator[:mygen, GenClassWithRange]
|
23
|
+
g.must_be_a_node
|
24
|
+
g.parameters.keys.include?(:range).must_be_true
|
25
|
+
g.parameters.range.required?.must_be_true
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require_relative '../helper.rb'
|
2
|
+
include Nodus::Nodes
|
3
|
+
|
4
|
+
describe Nodus::Nodes::Node do
|
5
|
+
subject do
|
6
|
+
Node.compose(:subject) do
|
7
|
+
param :p1, :required
|
8
|
+
param :p2, default: 10
|
9
|
+
param :p3, :optional, default: 100
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'is a node, with correct title' do
|
14
|
+
subject.must_be_a_node
|
15
|
+
subject.new.must_be_kind_of Node
|
16
|
+
subject.title.must_equal :subject
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'has the correct parameters' do
|
20
|
+
subject.parameters.keys.sort.must_equal [:p1, :p2, :p3]
|
21
|
+
subject.parameters.p3.default.must_equal 100
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'ensures that there is a title' do
|
25
|
+
->{Node.compose()}.must_raise ArgumentError
|
26
|
+
->{Node[]}.must_raise ArgumentError
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'children having children having children' do
|
30
|
+
ssubject = subject.compose(:ssubject)
|
31
|
+
ssubject.must_be_a_node
|
32
|
+
ssubject.parameters.keys.sort.must_equal [:p1, :p2, :p3]
|
33
|
+
ssubject.parameters.p3.default.must_equal 100
|
34
|
+
|
35
|
+
sssubject = ssubject.compose(:sssubject){ param :p4 }
|
36
|
+
sssubject.must_be_a_node
|
37
|
+
sssubject.parameters.keys.sort.must_equal [:p1, :p2, :p3, :p4]
|
38
|
+
sssubject.parameters.p3.default.must_equal 100
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'can be done using a more natural class syntax' do
|
42
|
+
class MySuperNode < Node.compose :my_super_node
|
43
|
+
param :p2, :hidden
|
44
|
+
end
|
45
|
+
MySuperNode.must_be_a_node
|
46
|
+
MySuperNode.parameters.p2.hidden?.must_be_true
|
47
|
+
remove_class(:MySuperNode)
|
48
|
+
|
49
|
+
class MyOtherNode < subject[:my_other_node]
|
50
|
+
param :pzz
|
51
|
+
end
|
52
|
+
MyOtherNode.must_be_a_node
|
53
|
+
MyOtherNode.parameters.pzz.must_be_kind_of Param
|
54
|
+
remove_class(:MyOtherNode)
|
55
|
+
|
56
|
+
class MyThirdNode < Node[:the_third]; end
|
57
|
+
MyThirdNode.must_be_a_node
|
58
|
+
MyThirdNode.parameters.must_be_empty
|
59
|
+
MyThirdNode.title.must_equal :the_third
|
60
|
+
|
61
|
+
remove_class(:MyThirdNode)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe Nodus::Nodes::ConcurrentNode do
|
66
|
+
let(:base_1) do
|
67
|
+
Node.compose(:base_1) do
|
68
|
+
param :p1, :required
|
69
|
+
param :p2, default: 10
|
70
|
+
param :p3, :optional, default: 100
|
71
|
+
end
|
72
|
+
end
|
73
|
+
let(:base_2){ Node.compose(:base_2){ [:p4,:p5,:p6].each{|p| param p} }}
|
74
|
+
let(:base_3){ Node.compose(:base_3){ [:p1,:p2,:p3].each{|p| param p, default: 20} }}
|
75
|
+
|
76
|
+
it 'is a kind of node' do
|
77
|
+
conc = ConcurrentNode.compose(:conc, base_1, base_2)
|
78
|
+
conc.must_be_a_node
|
79
|
+
conc.new.must_be_kind_of Node
|
80
|
+
conc.title.must_equal :conc
|
81
|
+
|
82
|
+
conc = ConcurrentNode[:conc, base_1, base_2]
|
83
|
+
conc.must_be_a_node
|
84
|
+
conc.new.must_be_kind_of Node
|
85
|
+
conc.title.must_equal :conc
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'also requires a title (for now)' do
|
89
|
+
->{ConcurrentNode.compose(base_1, base_2)}.must_raise ArgumentError
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'non-conflicting consolidated params act normal' do
|
93
|
+
skip
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'non-conflicting params can also be accessed with parent specifier' do
|
97
|
+
skip
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'can have its parameters curried ala basenode' do
|
101
|
+
skip
|
102
|
+
end
|
103
|
+
end
|