agreement-design-prototype 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -0
- data/agreement-design.gemspec +2 -1
- data/build/build_models.rb +24 -9
- data/gen/data/data.json +1 -0
- data/gen/data/data.yaml +46 -0
- data/gen/data/fm_agreements.jsonlines +2 -0
- data/gen/data/fm_catalogue.jsonlines +1 -0
- data/gen/diagrams/metamodel.dot +45 -0
- data/gen/doc/data.md +44 -0
- data/gen/doc/metamodel.md +99 -57
- data/gen/images/metamodel.jpg +0 -0
- data/model/agreement.rb +95 -43
- data/model/fm.rb +56 -24
- data/model/geographic.rb +30 -0
- data/model/party.rb +14 -12
- data/out/test/data/datatest.json +1 -0
- data/out/test/diagrams/d.dot +16 -30
- data/out/test/doc/doctest.md +17 -19
- data/out/test/doc/modeldoctest.md +62 -0
- data/out/test/images/d.jpg +0 -0
- data/src/api.rb +33 -0
- data/src/data.rb +89 -0
- data/src/data_model.rb +102 -38
- data/src/diagram.rb +61 -95
- data/src/doc.rb +55 -151
- data/src/transform.rb +144 -0
- data/test/data_model_test.rb +43 -65
- data/test/data_test.rb +26 -0
- data/test/diagram_test.rb +2 -5
- data/test/doc_test.rb +15 -9
- data/test/test_model.rb +88 -0
- data/test/transform_test.rb +83 -0
- metadata +21 -5
- data/gen/diagrams/data_model.dot +0 -34
- data/gen/doc/frameworks.md +0 -19
- data/gen/images/data_model.jpg +0 -0
data/src/diagram.rb
CHANGED
@@ -1,67 +1,13 @@
|
|
1
1
|
require 'fileutils'
|
2
|
-
require_relative '
|
2
|
+
require_relative 'transform'
|
3
|
+
include Transform
|
3
4
|
|
5
|
+
class Diagram < Output
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
super(file)
|
8
|
-
@node = node
|
9
|
-
pput %Q("#{@node}" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>#{@node}</TD></TH>)
|
10
|
-
@items = []
|
7
|
+
def initialize dir, name
|
8
|
+
super dir, name, ".dot"
|
11
9
|
end
|
12
10
|
|
13
|
-
def item i
|
14
|
-
@items << i
|
15
|
-
self.pput %Q(<TR><TD ALIGN="LEFT">-#{i}</TD></TR>)
|
16
|
-
end
|
17
|
-
|
18
|
-
def finish
|
19
|
-
# self.pput " " << @items.join( ",")
|
20
|
-
self.pput "</table>"
|
21
|
-
self.pput ">];\n"
|
22
|
-
end
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
class Graph < Element
|
28
|
-
def initialize file
|
29
|
-
super(file)
|
30
|
-
self.pput "strict digraph {\n"
|
31
|
-
end
|
32
|
-
|
33
|
-
def finish
|
34
|
-
self.pput "}\n"
|
35
|
-
end
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
class SubGraph < Element
|
40
|
-
def initialize file, model
|
41
|
-
super(file)
|
42
|
-
self.pput "subgraph cluster_#{model} {\n"
|
43
|
-
self.pput "node [shape=plaintext margin=0];\n"
|
44
|
-
self.pput "label=#{model};\n"
|
45
|
-
end
|
46
|
-
|
47
|
-
def finish
|
48
|
-
self.pput "}\n"
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
class Links < Element
|
54
|
-
|
55
|
-
def link el1, el2, label: el, arrowhead: nil, arrowtail: nil
|
56
|
-
ah = arrowhead ? %Q!arrowhead = "#{arrowhead}"! : ""
|
57
|
-
at = arrowtail ? %Q!arrowtail = "#{arrowtail}"! : ""
|
58
|
-
pput %Q!"#{el1}" -> "#{el2}" [label="#{label}" #{ah} #{at} ];\n!
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|
62
|
-
|
63
|
-
class Diagram < Doc
|
64
|
-
|
65
11
|
def dotfile
|
66
12
|
File.join(diagram_path, "#{self.name}.dot")
|
67
13
|
end
|
@@ -75,42 +21,56 @@ class Diagram < Doc
|
|
75
21
|
FileUtils.mkpath diagram_path
|
76
22
|
FileUtils.mkpath image_path
|
77
23
|
File.open(self.dotfile, "w") do |file|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
24
|
+
# nodes
|
25
|
+
file.print "strict digraph {\n"
|
26
|
+
|
27
|
+
transform_metamodel(
|
28
|
+
{
|
29
|
+
before_model: lambda do |model:|
|
30
|
+
file.print "subgraph cluster_#{model} {\n"
|
31
|
+
file.print "node [shape=plaintext margin=0];\n"
|
32
|
+
file.print "label=#{model};\n"
|
33
|
+
end,
|
34
|
+
after_model: lambda do |model:, before:|
|
35
|
+
file.print "}\n"
|
36
|
+
end,
|
37
|
+
before_type: lambda do |type:, depth:, index:, total:|
|
38
|
+
file.print %Q("#{type.typename}" [label=<<table BORDER="1" CELLBORDER="0" CELLSPACING="0"><TH><TD>#{type.typename}</TD></TH>)
|
39
|
+
end,
|
40
|
+
after_type: lambda do |type:, depth:, before:|
|
41
|
+
file.print "</table>"
|
42
|
+
file.print ">];\n"
|
43
|
+
end,
|
44
|
+
attribute: lambda do |id:, val:, depth:, type:, index:, total:|
|
45
|
+
file.print %Q(<TR><TD ALIGN="LEFT">-#{id}</TD></TR>)
|
46
|
+
end,
|
47
|
+
},
|
48
|
+
*models
|
49
|
+
)
|
50
|
+
# links
|
51
|
+
transform_metamodel(
|
52
|
+
{
|
53
|
+
before_type: lambda do |type:, depth:, index:, total:|
|
54
|
+
if type.superclass < DataType
|
55
|
+
link(file, type.typename, type.superclass.typename,
|
106
56
|
label: "extends",
|
107
57
|
arrowhead: "none",
|
108
|
-
arrowtail:
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
58
|
+
arrowtail: "normal")
|
59
|
+
end
|
60
|
+
end,
|
61
|
+
:attribute => lambda do |id:, val:, depth: 0, type:, index:, total:|
|
62
|
+
if val[:links] || val[:type] < DataType
|
63
|
+
contains = (val[:type] < DataType)
|
64
|
+
link(file, type.typename, val[:links] ? val[:links].typename : val[:type].typename,
|
65
|
+
label: %Q!#{contains ? "{contains} " : ""}#{val[:name]}!,
|
66
|
+
arrowhead: contains ? "none" : "open",
|
67
|
+
arrowtail: contains ? "diamond" : "none")
|
68
|
+
end
|
69
|
+
end,
|
70
|
+
},
|
71
|
+
*models
|
72
|
+
)
|
73
|
+
file.print "}\n"
|
114
74
|
end
|
115
75
|
|
116
76
|
unless system("dot -Tjpg #{self.dotfile} > #{self.jpgfile}")
|
@@ -136,11 +96,17 @@ class Diagram < Doc
|
|
136
96
|
private
|
137
97
|
|
138
98
|
def image_path
|
139
|
-
File.join(self.
|
99
|
+
File.join(self.dir, "images")
|
140
100
|
end
|
141
101
|
|
142
102
|
def diagram_path
|
143
|
-
File.join(self.
|
103
|
+
File.join(self.dir, "diagrams")
|
104
|
+
end
|
105
|
+
|
106
|
+
def link file, el1, el2, label: el2, arrowhead: nil, arrowtail: nil
|
107
|
+
ah = arrowhead ? %Q!arrowhead = "#{arrowhead}"! : ""
|
108
|
+
at = arrowtail ? %Q!arrowtail = "#{arrowtail}"! : ""
|
109
|
+
file.print %Q!"#{el1}" -> "#{el2}" [label="#{label}" #{ah} #{at} ];\n!
|
144
110
|
end
|
145
111
|
|
146
112
|
end
|
data/src/doc.rb
CHANGED
@@ -1,114 +1,61 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
@file = file
|
4
|
-
end
|
5
|
-
|
6
|
-
def finish
|
7
|
-
self
|
8
|
-
end
|
9
|
-
|
10
|
-
protected
|
11
|
-
|
12
|
-
def pput(string)
|
13
|
-
@file.print string
|
14
|
-
self
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
def cond_call(depth, id, value, lam, lambdas)
|
20
|
-
if lambdas[lam]
|
21
|
-
return lambdas[lam].(depth, id, value)
|
22
|
-
end
|
23
|
-
return 0
|
24
|
-
end
|
25
|
-
|
26
|
-
# each lambda takes the depth, id, value
|
27
|
-
# Lambdas are:
|
28
|
-
# :before_type_lambda,
|
29
|
-
# :after_type_lambda,
|
30
|
-
# :before_array_lambda,
|
31
|
-
# :after_array_lambda,
|
32
|
-
# :attribute_lambda
|
33
|
-
# which takes a type and returns an object (the id)
|
34
|
-
# depth is the number counting from zero, incremented with array elements and nested types.
|
35
|
-
# ID is a nested int of the type a for types and a.b for array types,
|
36
|
-
# and is the name of the attribute for attributes
|
37
|
-
# decl is the type or attribute or array
|
1
|
+
require_relative 'transform'
|
2
|
+
include Transform
|
38
3
|
|
39
|
-
|
4
|
+
class Document < Output
|
40
5
|
|
41
|
-
|
42
|
-
|
43
|
-
j = 1
|
44
|
-
for aa in decl
|
45
|
-
transform_type(depth , "#{id}.#{j}", aa, lambdas)
|
46
|
-
j = j + 1
|
47
|
-
end
|
48
|
-
cond_call(depth, id, decl, :after_array_lambda, lambdas)
|
49
|
-
elsif decl.class <= DataType
|
50
|
-
cond_call(depth, id, decl, :before_type_lambda, lambdas)
|
51
|
-
for ak in decl.attributes.keys
|
52
|
-
av = decl.attributes[ak]
|
53
|
-
transform_type(depth + 1, ak, av, lambdas)
|
54
|
-
end
|
55
|
-
cond_call(depth, id, decl, :after_type_lambda, lambdas)
|
56
|
-
else
|
57
|
-
cond_call(depth, id, decl, :attribute_lambda, lambdas)
|
6
|
+
def initialize dir, name
|
7
|
+
super File.join( dir, "doc"), name, "md"
|
58
8
|
end
|
59
|
-
self
|
60
|
-
end
|
61
|
-
|
62
|
-
class ClassNameElement < Element
|
63
9
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
10
|
+
def document *models
|
11
|
+
file do |file|
|
12
|
+
transform_datamodel(
|
13
|
+
{
|
14
|
+
:before_type => lambda do |type:, depth: 0, index:,total:|
|
15
|
+
file.print %Q!####{'#' * depth} #{type.name} #{type.attributes[:id] || ""} \n!
|
16
|
+
end,
|
17
|
+
:attribute => lambda do |id:, val:, depth: 0, type: nil, index:, total:|
|
18
|
+
file.print %Q!#{" " * depth} - #{id} #{val}\n!
|
19
|
+
end
|
20
|
+
}, *models)
|
68
21
|
end
|
69
22
|
end
|
70
23
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
pput "\n #{type.description}\n\n"
|
106
|
-
pput "|attribute|type|multiplicity|description|\n"
|
107
|
-
pput "|---------|----|------------|-----------|\n"
|
108
|
-
for a in type.attributes.values
|
109
|
-
pput "|#{a[:name]}|#{a[:type]}|#{self.multiplicity(a)}|#{a[:description]}|\n"
|
24
|
+
def document_metamodel *models
|
25
|
+
file do |file|
|
26
|
+
transform_metamodel(
|
27
|
+
{
|
28
|
+
before_model: lambda do |model:|
|
29
|
+
file.print %Q!\# Data model: #{model}\n!
|
30
|
+
end,
|
31
|
+
before_group: lambda do |group|
|
32
|
+
file.print %Q!\# #{cat.class}: #{cat.name}\n!
|
33
|
+
if (cat.respond_to?(:description))
|
34
|
+
file.print %Q! #{cat.description}\n!
|
35
|
+
end
|
36
|
+
end,
|
37
|
+
before_type: lambda do |type:, depth:, index:, total:|
|
38
|
+
file.print "## #{type.typename}"
|
39
|
+
if type.extends
|
40
|
+
file.print " extends #{type.extends}"
|
41
|
+
end
|
42
|
+
file.print "\n #{type.description}\n\n"
|
43
|
+
file.print "|attribute|type|multiplicity|description|\n"
|
44
|
+
file.print "|---------|----|------------|-----------|\n"
|
45
|
+
end,
|
46
|
+
attribute: lambda do |id:, val:, type:, depth:, index:, total:|
|
47
|
+
file.print "|#{val[:name]}|#{type_and_link(val)}|#{multiplicity(val)}|#{val[:description]}|\n"
|
48
|
+
end,
|
49
|
+
before_codes: lambda do |model:|
|
50
|
+
file.print "# Codes\n"
|
51
|
+
end,
|
52
|
+
code: lambda do |model:, code:|
|
53
|
+
file.print "## #{code[:id]} #{code[:title]}\n"
|
54
|
+
file.print "#{code[:description]}\n"
|
55
|
+
file.print "#{code[:uri]}\n"
|
56
|
+
end
|
57
|
+
}, *models)
|
110
58
|
end
|
111
|
-
self
|
112
59
|
end
|
113
60
|
|
114
61
|
def multiplicity m
|
@@ -125,57 +72,14 @@ class MetaTypeElement < Element
|
|
125
72
|
end
|
126
73
|
return m.to_s
|
127
74
|
end
|
128
|
-
end
|
129
|
-
|
130
|
-
class Doc
|
131
|
-
attr_accessor :path, :name
|
132
75
|
|
133
|
-
|
134
|
-
self.path = path
|
135
|
-
self.name = name
|
136
|
-
end
|
76
|
+
private
|
137
77
|
|
138
|
-
def
|
139
|
-
|
140
|
-
|
141
|
-
for model in models
|
142
|
-
dom = ClassNameElement.new(file)
|
143
|
-
dom.write model
|
144
|
-
for typename in model.contents.keys
|
145
|
-
grp = GroupElement.new(file)
|
146
|
-
grp.write typename
|
147
|
-
i = 1
|
148
|
-
for decl in model.contents[typename]
|
149
|
-
TypeDeclElement.new(file).write(0, i, decl).finish
|
150
|
-
i = i + 1
|
151
|
-
end
|
152
|
-
grp.finish
|
153
|
-
end
|
154
|
-
dom.finish
|
155
|
-
end
|
78
|
+
def type_and_link(val)
|
79
|
+
if val[:links]
|
80
|
+
return "#{val[:type]} -> #{val[:links]}"
|
156
81
|
end
|
157
|
-
|
158
|
-
|
159
|
-
def document_metamodel *models
|
160
|
-
FileUtils.mkpath doc_path
|
161
|
-
File.open(self.docfile, "w") do |file|
|
162
|
-
for model in models
|
163
|
-
dom = GroupElement.new(file)
|
164
|
-
dom.write model.name, level: 1
|
165
|
-
for type in model.types.values
|
166
|
-
MetaTypeElement.new(file).write(type).finish
|
167
|
-
end
|
168
|
-
dom.finish
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
def docfile
|
174
|
-
File.join(doc_path, "#{self.name}.md")
|
175
|
-
end
|
176
|
-
|
177
|
-
def doc_path
|
178
|
-
File.join(self.path, "doc")
|
82
|
+
val[:type]
|
179
83
|
end
|
180
84
|
|
181
85
|
end
|
data/src/transform.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
module Transform
|
2
|
+
|
3
|
+
class Output
|
4
|
+
public
|
5
|
+
|
6
|
+
attr_accessor :dir, :name, :ext
|
7
|
+
|
8
|
+
def initialize dir, name, ext
|
9
|
+
self.dir = dir
|
10
|
+
self.name = name
|
11
|
+
self.ext = ext
|
12
|
+
end
|
13
|
+
|
14
|
+
def filepath
|
15
|
+
File.join(self.dir, "#{name}.#{ext}")
|
16
|
+
end
|
17
|
+
|
18
|
+
def file(&block)
|
19
|
+
FileUtils.mkpath dir
|
20
|
+
File.open(filepath, "w", &block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def before_model_lambda model: nil
|
26
|
+
return [model: model]
|
27
|
+
end
|
28
|
+
|
29
|
+
def after_model_lambda model: nil, before: nil
|
30
|
+
return [model: model, before: before]
|
31
|
+
end
|
32
|
+
|
33
|
+
def before_codes_lambda model: nil
|
34
|
+
return [model: model]
|
35
|
+
end
|
36
|
+
|
37
|
+
def code_lambda model: nil, code: nil
|
38
|
+
return [model: model, code: code]
|
39
|
+
end
|
40
|
+
|
41
|
+
def before_group_lambda name: nil, depth: 0
|
42
|
+
return [name: name, depth: depth]
|
43
|
+
end
|
44
|
+
|
45
|
+
def after_group_lambda name: nil, before: nil, depth: 0
|
46
|
+
return [name: name, before: before, depth: depth]
|
47
|
+
end
|
48
|
+
|
49
|
+
def before_type_lambda type: nil, depth: 0, index: 0, total: 1
|
50
|
+
return [type: type, depth: depth, index: index, total: total]
|
51
|
+
end
|
52
|
+
|
53
|
+
def after_type_lambda type: nil, before: nil, depth: 0
|
54
|
+
return [type: type, before: before, depth: depth]
|
55
|
+
end
|
56
|
+
|
57
|
+
def before_array_lambda name: nil, decl: nil, depth: 0, total: 1
|
58
|
+
return [name: name, decl: decl, depth: depth, total: total]
|
59
|
+
end
|
60
|
+
|
61
|
+
def after_array_lambda index: 0, decl: nil, depth: 0, before: nil
|
62
|
+
return [index: index, decl: decl, depth: depth, before: before]
|
63
|
+
end
|
64
|
+
|
65
|
+
def attribute_lambda id:, val:, depth: 0, type: nil, index:, total:
|
66
|
+
return [id: id, val: val, depth: depth, type: type, index: index, total: total]
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param models - the models to transform
|
70
|
+
# @lambdas - set of function callbacks to transform
|
71
|
+
|
72
|
+
def transform_datamodel lambdas, *models
|
73
|
+
for model in models
|
74
|
+
dom = cond_call(lambdas, :before_model, *before_model_lambda(model: model))
|
75
|
+
for typename in model.contents.keys
|
76
|
+
grp = cond_call(lambdas, :before_group, *before_group_lambda(name: typename))
|
77
|
+
t = 0
|
78
|
+
# there will be more than one of each type
|
79
|
+
for type in model.contents[typename]
|
80
|
+
transform_type(lambdas, decl: type, name: typename, depth: 0, index: 0, total: 1)
|
81
|
+
t = t + 1
|
82
|
+
end
|
83
|
+
grp = cond_call(lambdas, :after_group, *after_group_lambda(name: typename, before: grp))
|
84
|
+
end
|
85
|
+
cond_call(lambdas, :after_model, *after_model_lambda(model: model, before: dom))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def transform_metamodel lambdas, *models
|
90
|
+
for model in models
|
91
|
+
dom = cond_call(lambdas, :before_model, *before_model_lambda(model: model))
|
92
|
+
for type in model.types.values
|
93
|
+
before = cond_call(lambdas, :before_type, *before_type_lambda(type: type, index:0, total:1))
|
94
|
+
i = 0
|
95
|
+
for ak in type.attributes.keys
|
96
|
+
av = type.attributes[ak]
|
97
|
+
cond_call(lambdas, :attribute, *attribute_lambda(id: ak, val: av, type: type, index: i, total: type.attributes.keys.length))
|
98
|
+
i = i + 1
|
99
|
+
end
|
100
|
+
cond_call(lambdas, :after_type, *after_type_lambda(type: type, before: before))
|
101
|
+
end
|
102
|
+
cond_call(lambdas, :after_model, *after_model_lambda(model: model, before: dom))
|
103
|
+
if model.respond_to? :codes
|
104
|
+
cond_call(lambdas, :before_codes, *before_codes_lambda(model: model))
|
105
|
+
for code in model.codes.values
|
106
|
+
cond_call(lambdas, :code, *code_lambda(model: model, code: code))
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def cond_call(lambdas, lam, *args)
|
113
|
+
if lambdas[lam]
|
114
|
+
return lambdas[lam].(*args)
|
115
|
+
end
|
116
|
+
return 0
|
117
|
+
end
|
118
|
+
|
119
|
+
def transform_type(lambdas, index:, name:, decl:, depth:, total:)
|
120
|
+
|
121
|
+
if decl.class <= Array
|
122
|
+
arrctx = cond_call(lambdas, :before_array, *before_array_lambda(name: name, decl: decl, depth: depth, total: decl.length))
|
123
|
+
j = 0
|
124
|
+
for aa in decl
|
125
|
+
transform_type(lambdas, index: j, decl: aa, name: name, depth: depth, total: decl.length)
|
126
|
+
j = j + 1
|
127
|
+
end
|
128
|
+
cond_call(lambdas, :after_array, *after_array_lambda(index: index, decl: decl, depth: depth, before: arrctx))
|
129
|
+
elsif decl.class <= DataType
|
130
|
+
before = cond_call(lambdas, :before_type, *before_type_lambda(type: decl, depth: depth, index: index, total: total))
|
131
|
+
i = 0
|
132
|
+
for ak in decl.attributes.keys
|
133
|
+
av = decl.attributes[ak]
|
134
|
+
transform_type(lambdas, name: ak, decl: av, depth: depth + 1, index: i, total: decl.attributes.keys.length)
|
135
|
+
i = i + 1
|
136
|
+
end
|
137
|
+
cond_call(lambdas, :after_type, *after_type_lambda(type: decl, depth: depth, before: before))
|
138
|
+
else
|
139
|
+
cond_call(lambdas, :attribute, *attribute_lambda(id: name, val: decl, index: index, depth: depth, total: total))
|
140
|
+
end
|
141
|
+
self
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|