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.
@@ -0,0 +1,6 @@
1
+ module Nodus
2
+ module Version
3
+ VFILE = File.join(File.dirname(__FILE__),'..','VERSION')
4
+ VERSION = File.exist?(VFILE) ? File.read(VFILE).strip : `git -C '#{File.dirname(__FILE__)}' describe --tags`.strip
5
+ end
6
+ end
@@ -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
@@ -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