namo.rb 0.31.7
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/CHANGELOG +566 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +1268 -0
- data/Rakefile +42 -0
- data/lib/Namo/Collection.rb +130 -0
- data/lib/Namo/Enumerable.rb +83 -0
- data/lib/Namo/Formulae.rb +137 -0
- data/lib/Namo/Formulary.rb +6 -0
- data/lib/Namo/NegatedDimension.rb +14 -0
- data/lib/Namo/Row.rb +93 -0
- data/lib/Namo/VERSION.rb +6 -0
- data/lib/Symbol.rb +8 -0
- data/lib/namo.rb +433 -0
- data/namo.rb.gemspec +42 -0
- data/test/Namo/Collection_test.rb +548 -0
- data/test/Namo/Formulae_test.rb +458 -0
- data/test/Namo/NegatedDimension_test.rb +13 -0
- data/test/Namo/Row_test.rb +487 -0
- data/test/Symbol_test.rb +16 -0
- data/test/console_test.rb +44 -0
- data/test/demo_test.rb +86 -0
- data/test/namo_test.rb +3328 -0
- data/test/sizing_test.rb +40 -0
- metadata +107 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Rakefile
|
|
2
|
+
|
|
3
|
+
require 'rake/testtask'
|
|
4
|
+
|
|
5
|
+
Rake::TestTask.new(:test) do |t|
|
|
6
|
+
t.test_files = FileList['test/**/*_test.rb']
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
namespace :docs do
|
|
10
|
+
SOURCE_DOCS = %w{COMPARISON EXAMPLES README ROADMAP}
|
|
11
|
+
|
|
12
|
+
desc "Strip syntax highlighting from code blocks for printing"
|
|
13
|
+
task :md4print do
|
|
14
|
+
SOURCE_DOCS.each do |name|
|
|
15
|
+
sh "script/md4print #{name}.md"
|
|
16
|
+
sh "mv #{name}.print.md docs/"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "Render print-ready markdown to PDF"
|
|
21
|
+
task :md2pdf => :md4print do
|
|
22
|
+
Dir.glob('docs/*.print.md').each do |f|
|
|
23
|
+
pdf = f.sub(/\.md$/, '.pdf')
|
|
24
|
+
sh "pandoc #{f} --pdf-engine=xelatex --include-in-header script/print.preamble.tex -V geometry:margin=1in -V mainfont=Charter -V monofont=Menlo -o #{pdf}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
desc "Remove intermediate .print.md files"
|
|
29
|
+
task :clean do
|
|
30
|
+
rm_f Dir.glob('docs/*.print.md')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
desc "Remove all generated docs (intermediates and PDFs)"
|
|
34
|
+
task :clobber => :clean do
|
|
35
|
+
rm_f Dir.glob('docs/*.print.pdf')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
desc "Regenerate all derived docs"
|
|
39
|
+
task :gen => [:md2pdf, :clean]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
task default: :test
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Namo/Collection.rb
|
|
2
|
+
# Namo::Collection
|
|
3
|
+
|
|
4
|
+
class Namo
|
|
5
|
+
class Collection < Namo
|
|
6
|
+
attr_reader :members
|
|
7
|
+
|
|
8
|
+
def <<(*constituents)
|
|
9
|
+
constituents.flatten.each do |constituent|
|
|
10
|
+
case constituent
|
|
11
|
+
when Namo then add_member(constituent)
|
|
12
|
+
when Module then attach(constituent)
|
|
13
|
+
when Hash, Row
|
|
14
|
+
raise ArgumentError, "a Collection's rows come from its members; add a member (a named Namo), not a loose row"
|
|
15
|
+
else raise TypeError, "can't append #{constituent.class} to a Collection"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
@data = detail.data
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def find(name)
|
|
23
|
+
@members.find{|member| member.name == name} unless name.nil?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def summary(dimension = nil, by: :member, reducer: :sum, &block)
|
|
27
|
+
raise ArgumentError, "summary needs a dimension or a block" unless dimension || block
|
|
28
|
+
rows = (
|
|
29
|
+
if block
|
|
30
|
+
@members.map{|member| block.call(member).merge(by => member.name)}
|
|
31
|
+
else
|
|
32
|
+
@members.map{|member| {by => member.name, dimension => member.values(dimension).send(reducer)}}
|
|
33
|
+
end
|
|
34
|
+
)
|
|
35
|
+
Namo.new(rows)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def detail(positional_by = nil, by: :member)
|
|
39
|
+
by = positional_by || by
|
|
40
|
+
rows = (
|
|
41
|
+
@members.flat_map do |member|
|
|
42
|
+
member.data.map{|row| row.key?(by) ? row : row.merge(by => member.name)}
|
|
43
|
+
end
|
|
44
|
+
)
|
|
45
|
+
Namo.new(rows, formulae: member_formulae)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def as_summary(dimension = nil, by: :member, reducer: :sum, &block)
|
|
49
|
+
@data = summary(dimension, by: by, reducer: reducer, &block).data
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def as_detail(positional_by = nil, by: :member)
|
|
54
|
+
by = positional_by || by
|
|
55
|
+
view = detail(by)
|
|
56
|
+
@data = view.data
|
|
57
|
+
@formulae = @formulae.merge(view.formulae)
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The members are the substance, so they are what is rendered. A list of names
|
|
62
|
+
# and a row count described the derived view and called it the object, and the
|
|
63
|
+
# list was unbounded besides: 2,641 members emitted 22,704 characters on one
|
|
64
|
+
# line, which is the failure INSPECTED_ROWS was introduced to end.
|
|
65
|
+
#
|
|
66
|
+
# That budget is now spread across the members shown rather than spent on one
|
|
67
|
+
# member's rows: one member renders ten of its rows, ten members render one
|
|
68
|
+
# apiece, five render two.
|
|
69
|
+
def inspect
|
|
70
|
+
"#<#{self.class}#{inspected_name} #{inspected_members} #{@data.length} rows#{inspected_derived}>"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
protected
|
|
74
|
+
|
|
75
|
+
# A row operation on a Collection yields a Namo: 0.18.0 has it read the detail
|
|
76
|
+
# view and behave as the detail, and the detail is a Namo. Built as a
|
|
77
|
+
# Collection it would be one with no members, answering summary with an empty
|
|
78
|
+
# Namo rather than saying it cannot.
|
|
79
|
+
def return_class
|
|
80
|
+
Namo
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
# The members' formulae, folded in member order so that a later member wins a
|
|
86
|
+
# name collision, as a later member wins a collision of names in <<. The
|
|
87
|
+
# detail view is the members' rows, so it must be able to answer what the
|
|
88
|
+
# members can answer of them; group_by hands every member the same formulae,
|
|
89
|
+
# so only an assembled Collection can have members which disagree.
|
|
90
|
+
def member_formulae
|
|
91
|
+
@members.map(&:formulae).reduce(Formulae.new){|merged, formulae| merged.merge(formulae)}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def inspected_members
|
|
95
|
+
return '[]' if @members.empty?
|
|
96
|
+
shown = [@members.length, INSPECTED_ROWS].min
|
|
97
|
+
each = [INSPECTED_ROWS / shown, 1].max
|
|
98
|
+
rendered = @members.first(shown).map{|member| " #{member.name.inspect} => #{inspected_member(member, each)}"}.join(",\n")
|
|
99
|
+
rendered += "\n ... #{@members.length - shown} more members" if @members.length > shown
|
|
100
|
+
"[\n#{rendered}\n]"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# A member showing a single row is rendered on one line: three lines of brackets
|
|
104
|
+
# to carry one row is the shape a Collection of many members lands in, and it
|
|
105
|
+
# cost more than the rows did. On what is rendered rather than on the limit,
|
|
106
|
+
# so a member holding one row of a larger allowance reads the same way.
|
|
107
|
+
def inspected_member(member, limit)
|
|
108
|
+
rows, remainder, unshown = member.inspected_data(limit)
|
|
109
|
+
elided = remainder > 0
|
|
110
|
+
suffix = inspected_derived_names(unshown)
|
|
111
|
+
if rows.length == 1
|
|
112
|
+
"[#{rows.join}#{elided ? ", ... #{remainder} more rows" : ''}]#{suffix}"
|
|
113
|
+
else
|
|
114
|
+
body = rows.map{|row| " #{row}"}.join(",\n")
|
|
115
|
+
body += "\n ... #{remainder} more rows" if elided
|
|
116
|
+
"[\n#{body}\n ]#{suffix}"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def initialize(positional_data = nil, data: [], formulae: {}, name: nil)
|
|
121
|
+
@members = []
|
|
122
|
+
super
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def add_member(member)
|
|
126
|
+
@members.reject!{|existing| existing.name == member.name} unless member.name.nil?
|
|
127
|
+
@members << member
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Namo/Enumerable.rb
|
|
2
|
+
# Namo::Enumerable
|
|
3
|
+
|
|
4
|
+
class Namo
|
|
5
|
+
module Enumerable
|
|
6
|
+
include ::Enumerable
|
|
7
|
+
|
|
8
|
+
def each(&block)
|
|
9
|
+
return enum_for(:each) unless block_given?
|
|
10
|
+
@data.each{|row_data| block.call(Row.new(row_data, @formulae, self))}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def select(&block)
|
|
14
|
+
return_class.new(@data.select{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
|
|
15
|
+
end
|
|
16
|
+
alias_method :filter, :select
|
|
17
|
+
alias_method :find_all, :select
|
|
18
|
+
|
|
19
|
+
def reject(&block)
|
|
20
|
+
return_class.new(@data.reject{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def sort_by(&block)
|
|
24
|
+
return_class.new(@data.sort_by{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def first(n = nil)
|
|
28
|
+
if n
|
|
29
|
+
return_class.new(@data.first(n), formulae: @formulae.dup)
|
|
30
|
+
else
|
|
31
|
+
@data.first ? Row.new(@data.first, @formulae, self) : nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def last(n = nil)
|
|
36
|
+
if n
|
|
37
|
+
return_class.new(@data.last(n), formulae: @formulae.dup)
|
|
38
|
+
else
|
|
39
|
+
@data.last ? Row.new(@data.last, @formulae, self) : nil
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def take(n)
|
|
44
|
+
return_class.new(@data.take(n), formulae: @formulae.dup)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def drop(n)
|
|
48
|
+
return_class.new(@data.drop(n), formulae: @formulae.dup)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def take_while(&block)
|
|
52
|
+
return_class.new(@data.take_while{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def drop_while(&block)
|
|
56
|
+
return_class.new(@data.drop_while{|row| block.call(Row.new(row, @formulae, self))}, formulae: @formulae.dup)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def uniq(&block)
|
|
60
|
+
rows = block ? @data.uniq{|row| block.call(Row.new(row, @formulae, self))} : @data.uniq
|
|
61
|
+
return_class.new(rows, formulae: @formulae.dup)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def partition(&block)
|
|
65
|
+
matches, non_matches = @data.partition{|row| block.call(Row.new(row, @formulae, self))}
|
|
66
|
+
[
|
|
67
|
+
return_class.new(matches, formulae: @formulae.dup),
|
|
68
|
+
return_class.new(non_matches, formulae: @formulae.dup),
|
|
69
|
+
]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def group_by(dimension)
|
|
73
|
+
collection = Collection.new
|
|
74
|
+
source = derived_dimensions.include?(dimension) ? self[*data_dimensions, dimension] : self
|
|
75
|
+
members = (
|
|
76
|
+
source.data.group_by{|row_data| row_data[dimension]}.map do |value, rows|
|
|
77
|
+
return_class.new(rows, formulae: source.formulae.dup, name: value)
|
|
78
|
+
end
|
|
79
|
+
)
|
|
80
|
+
collection << members
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Namo/Formulae.rb
|
|
2
|
+
# Namo::Formulae
|
|
3
|
+
|
|
4
|
+
class Namo
|
|
5
|
+
class Formulae
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
def [](name)
|
|
9
|
+
@store[name]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def []=(name, callable)
|
|
13
|
+
@store[name] = callable
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def attach(modul)
|
|
17
|
+
raise_unless_formulary(modul)
|
|
18
|
+
modul.public_instance_methods(false).each{|name| bind(name, modul)}
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
alias_method :<<, :attach
|
|
22
|
+
|
|
23
|
+
def detach(constituent)
|
|
24
|
+
case constituent
|
|
25
|
+
when Symbol
|
|
26
|
+
@store.delete(constituent)
|
|
27
|
+
when Module
|
|
28
|
+
raise_unless_formulary(constituent)
|
|
29
|
+
constituent.public_instance_methods(false).each{|name| @store.delete(name)}
|
|
30
|
+
else
|
|
31
|
+
raise TypeError, "can't detach #{constituent.class} from a Formulae; expected a Symbol or a Module (formulary)"
|
|
32
|
+
end
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def derive(name, row, namo, *arguments)
|
|
37
|
+
formula = self[name]
|
|
38
|
+
if collection_scoped?(name)
|
|
39
|
+
raise_unless_namo_context(name, namo)
|
|
40
|
+
formula.call(row, namo, *arguments)
|
|
41
|
+
else
|
|
42
|
+
formula.call(row)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def required_parameter_count(name)
|
|
47
|
+
formula = self[name]
|
|
48
|
+
formula.arity >= 0 ? formula.arity : -formula.arity - 1
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def keys
|
|
52
|
+
@store.keys
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def key?(name)
|
|
56
|
+
@store.key?(name)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def empty?
|
|
60
|
+
@store.empty?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def each(&block)
|
|
64
|
+
return enum_for(:each) unless block_given?
|
|
65
|
+
@store.each(&block)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def delete(name)
|
|
69
|
+
@store.delete(name)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def merge(other)
|
|
73
|
+
self.class.new(@store.merge(other.to_h))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def reject(&block)
|
|
77
|
+
self.class.new(@store.reject(&block))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def dup
|
|
81
|
+
self.class.new(@store.dup)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def to_h
|
|
85
|
+
@store.dup
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def ==(other)
|
|
89
|
+
other.is_a?(Formulae) && keys.sort == other.keys.sort
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def eql?(other)
|
|
93
|
+
self == other
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def hash
|
|
97
|
+
keys.sort.hash
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def inspect
|
|
101
|
+
"#<#{self.class} #{@store.keys.inspect}>"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def initialize(store = {})
|
|
107
|
+
@store = store
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def host
|
|
111
|
+
@host ||= Object.new
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def bind(name, modul)
|
|
115
|
+
raise_unless_formulary(modul)
|
|
116
|
+
host.extend(modul)
|
|
117
|
+
self[name] = modul.instance_method(name).bind(host)
|
|
118
|
+
self
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def raise_unless_formulary(modul)
|
|
122
|
+
unless modul.include?(Namo::Formulary)
|
|
123
|
+
raise ArgumentError, "not a Namo::Formulary: #{modul}"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def collection_scoped?(name)
|
|
128
|
+
required_parameter_count(name) >= 2
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def raise_unless_namo_context(name, namo)
|
|
132
|
+
unless namo
|
|
133
|
+
raise ArgumentError, "collection-scoped formula #{name.inspect} requires a Namo context, but this Row has none"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
data/lib/Namo/Row.rb
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Namo/Row.rb
|
|
2
|
+
# Namo::Row
|
|
3
|
+
|
|
4
|
+
class Namo
|
|
5
|
+
class Row
|
|
6
|
+
def [](name, *arguments)
|
|
7
|
+
raise_unless_expected_arguments(name, arguments)
|
|
8
|
+
if @formulae.key?(name)
|
|
9
|
+
@formulae.derive(name, self, @namo, *arguments)
|
|
10
|
+
else
|
|
11
|
+
@row[name]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def ==(other)
|
|
16
|
+
other.is_a?(Row) && @row == other.to_h
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def eql?(other)
|
|
20
|
+
other.is_a?(Row) && @row.eql?(other.to_h)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def hash
|
|
24
|
+
@row.hash
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def match?(selections)
|
|
28
|
+
selections.all? do |dimension, coordinate|
|
|
29
|
+
case coordinate
|
|
30
|
+
when Array, Range
|
|
31
|
+
coordinate.include?(self[dimension])
|
|
32
|
+
when Proc
|
|
33
|
+
coordinate.call(self[dimension])
|
|
34
|
+
when Regexp
|
|
35
|
+
coordinate.match?(self[dimension].to_s)
|
|
36
|
+
else
|
|
37
|
+
self[dimension] == coordinate
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_h
|
|
43
|
+
@row
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def inspect
|
|
47
|
+
"#<#{self.class} #{inspected_row}#{inspected_derived}>"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def inspected_row
|
|
53
|
+
@row.merge(inspected_derivations).inspect
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# As Namo#inspected_derivations: the value where there is one to show, and
|
|
57
|
+
# the derived list to name it either way.
|
|
58
|
+
def inspected_derivations
|
|
59
|
+
@formulae.keys.each_with_object({}) do |dimension, derived|
|
|
60
|
+
next if @formulae.required_parameter_count(dimension) > 2
|
|
61
|
+
derived[dimension] = self[dimension]
|
|
62
|
+
rescue StandardError
|
|
63
|
+
next
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def inspected_derived
|
|
68
|
+
@formulae.keys.empty? ? '' : " derived: #{@formulae.keys.inspect}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def initialize(row, formulae, namo = nil)
|
|
72
|
+
@row = row
|
|
73
|
+
@formulae = formulae
|
|
74
|
+
@namo = namo
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def expected_argument_counts(name)
|
|
78
|
+
return [0, 0] unless @formulae.key?(name)
|
|
79
|
+
count = @formulae.required_parameter_count(name)
|
|
80
|
+
return [0, 0] unless count >= 2
|
|
81
|
+
minimum = count - 2
|
|
82
|
+
maximum = @formulae[name].arity >= 0 ? minimum : nil
|
|
83
|
+
[minimum, maximum]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def raise_unless_expected_arguments(name, arguments)
|
|
87
|
+
minimum, maximum = expected_argument_counts(name)
|
|
88
|
+
return if arguments.length >= minimum && (maximum.nil? || arguments.length <= maximum)
|
|
89
|
+
expected = maximum.nil? ? "#{minimum}+" : minimum.to_s
|
|
90
|
+
raise ArgumentError, "wrong number of arguments for #{name.inspect} (given #{arguments.length}, expected #{expected})"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
data/lib/Namo/VERSION.rb
ADDED