rggen-core 0.9.0
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/CODE_OF_CONDUCT.md +46 -0
- data/LICENSE +21 -0
- data/README.md +46 -0
- data/exe/rggen +13 -0
- data/lib/rggen/core.rb +101 -0
- data/lib/rggen/core/base/component.rb +54 -0
- data/lib/rggen/core/base/component_factory.rb +84 -0
- data/lib/rggen/core/base/feature.rb +46 -0
- data/lib/rggen/core/base/feature_factory.rb +37 -0
- data/lib/rggen/core/base/hierarchical_accessors.rb +91 -0
- data/lib/rggen/core/base/hierarchical_feature_accessors.rb +83 -0
- data/lib/rggen/core/base/internal_struct.rb +21 -0
- data/lib/rggen/core/base/shared_context.rb +18 -0
- data/lib/rggen/core/builder.rb +13 -0
- data/lib/rggen/core/builder/builder.rb +150 -0
- data/lib/rggen/core/builder/category.rb +116 -0
- data/lib/rggen/core/builder/component_entry.rb +29 -0
- data/lib/rggen/core/builder/component_registry.rb +47 -0
- data/lib/rggen/core/builder/feature_registry.rb +126 -0
- data/lib/rggen/core/builder/input_component_registry.rb +35 -0
- data/lib/rggen/core/builder/list_feature_entry.rb +98 -0
- data/lib/rggen/core/builder/output_component_registry.rb +10 -0
- data/lib/rggen/core/builder/simple_feature_entry.rb +39 -0
- data/lib/rggen/core/cli.rb +29 -0
- data/lib/rggen/core/configuration.rb +19 -0
- data/lib/rggen/core/configuration/component.rb +10 -0
- data/lib/rggen/core/configuration/component_factory.rb +19 -0
- data/lib/rggen/core/configuration/error.rb +16 -0
- data/lib/rggen/core/configuration/feature.rb +13 -0
- data/lib/rggen/core/configuration/feature_factory.rb +11 -0
- data/lib/rggen/core/configuration/hash_loader.rb +15 -0
- data/lib/rggen/core/configuration/json_loader.rb +18 -0
- data/lib/rggen/core/configuration/loader.rb +11 -0
- data/lib/rggen/core/configuration/ruby_loader.rb +15 -0
- data/lib/rggen/core/configuration/yaml_loader.rb +18 -0
- data/lib/rggen/core/core_extensions/casecmp.rb +12 -0
- data/lib/rggen/core/core_extensions/forwardable.rb +12 -0
- data/lib/rggen/core/core_extensions/forwardable_workaround.rb +22 -0
- data/lib/rggen/core/core_extensions/object.rb +14 -0
- data/lib/rggen/core/dsl.rb +30 -0
- data/lib/rggen/core/exceptions.rb +29 -0
- data/lib/rggen/core/facets.rb +7 -0
- data/lib/rggen/core/generator.rb +51 -0
- data/lib/rggen/core/input_base/component.rb +30 -0
- data/lib/rggen/core/input_base/component_factory.rb +88 -0
- data/lib/rggen/core/input_base/feature.rb +130 -0
- data/lib/rggen/core/input_base/feature_factory.rb +80 -0
- data/lib/rggen/core/input_base/input_data.rb +98 -0
- data/lib/rggen/core/input_base/input_matcher.rb +79 -0
- data/lib/rggen/core/input_base/input_value.rb +34 -0
- data/lib/rggen/core/input_base/json_loader.rb +16 -0
- data/lib/rggen/core/input_base/loader.rb +44 -0
- data/lib/rggen/core/input_base/property.rb +76 -0
- data/lib/rggen/core/input_base/verifier.rb +41 -0
- data/lib/rggen/core/input_base/yaml_loader.rb +34 -0
- data/lib/rggen/core/options.rb +181 -0
- data/lib/rggen/core/output_base/code_generator.rb +59 -0
- data/lib/rggen/core/output_base/component.rb +100 -0
- data/lib/rggen/core/output_base/component_factory.rb +35 -0
- data/lib/rggen/core/output_base/erb_engine.rb +21 -0
- data/lib/rggen/core/output_base/feature.rb +147 -0
- data/lib/rggen/core/output_base/feature_factory.rb +13 -0
- data/lib/rggen/core/output_base/file_writer.rb +40 -0
- data/lib/rggen/core/output_base/template_engine.rb +27 -0
- data/lib/rggen/core/printers.rb +53 -0
- data/lib/rggen/core/register_map.rb +21 -0
- data/lib/rggen/core/register_map/component.rb +20 -0
- data/lib/rggen/core/register_map/component_factory.rb +19 -0
- data/lib/rggen/core/register_map/error.rb +16 -0
- data/lib/rggen/core/register_map/feature.rb +22 -0
- data/lib/rggen/core/register_map/feature_factory.rb +11 -0
- data/lib/rggen/core/register_map/hash_loader.rb +47 -0
- data/lib/rggen/core/register_map/input_data.rb +34 -0
- data/lib/rggen/core/register_map/json_loader.rb +18 -0
- data/lib/rggen/core/register_map/loader.rb +15 -0
- data/lib/rggen/core/register_map/ruby_loader.rb +15 -0
- data/lib/rggen/core/register_map/yaml_loader.rb +18 -0
- data/lib/rggen/core/utility/attribute_setter.rb +53 -0
- data/lib/rggen/core/utility/code_utility.rb +64 -0
- data/lib/rggen/core/utility/code_utility/code_block.rb +88 -0
- data/lib/rggen/core/utility/code_utility/line.rb +51 -0
- data/lib/rggen/core/utility/code_utility/source_file.rb +104 -0
- data/lib/rggen/core/utility/code_utility/structure_definition.rb +54 -0
- data/lib/rggen/core/utility/regexp_patterns.rb +38 -0
- data/lib/rggen/core/version.rb +10 -0
- metadata +188 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RgGen
|
4
|
+
module Core
|
5
|
+
module InputBase
|
6
|
+
class Property
|
7
|
+
def self.define(feature, name, **options, &body)
|
8
|
+
new(name, options, body).define(feature)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(name, options, body)
|
12
|
+
@name = name
|
13
|
+
@options = options
|
14
|
+
@costom_property = create_costom_property(@options[:body] || body)
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :name
|
18
|
+
|
19
|
+
def define(feature)
|
20
|
+
feature.class_exec(self) do |property|
|
21
|
+
define_method(property.name) do |*args, &block|
|
22
|
+
property.evaluate(self, args, block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def evaluate(feature, args, block)
|
28
|
+
feature.verify(@options[:verify]) if @options.key?(:verify)
|
29
|
+
if proxy_property?
|
30
|
+
proxy_property(feature, args, block)
|
31
|
+
else
|
32
|
+
default_property(feature)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def create_costom_property(body)
|
39
|
+
body && Module.new.module_eval do
|
40
|
+
define_method(:__costom_property__, &body)
|
41
|
+
instance_method(:__costom_property__)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def proxy_property?
|
46
|
+
[
|
47
|
+
@costom_property,
|
48
|
+
@options[:forward_to_helper],
|
49
|
+
@options[:forward_to]
|
50
|
+
].any?
|
51
|
+
end
|
52
|
+
|
53
|
+
def proxy_property(feature, args, block)
|
54
|
+
receiver, method =
|
55
|
+
if @costom_property
|
56
|
+
[@costom_property.bind(feature), :call]
|
57
|
+
elsif @options[:forward_to_helper]
|
58
|
+
[feature.class, @name]
|
59
|
+
else
|
60
|
+
[feature, @options[:forward_to]]
|
61
|
+
end
|
62
|
+
receiver.__send__(method, *args, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
def default_property(feature)
|
66
|
+
varible_name = "@#{@name[-1] == '?' ? @name[0..-2] : @name}"
|
67
|
+
if feature.instance_variable_defined?(varible_name)
|
68
|
+
feature.instance_variable_get(varible_name)
|
69
|
+
else
|
70
|
+
@options[:default]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RgGen
|
4
|
+
module Core
|
5
|
+
module InputBase
|
6
|
+
class Verifier
|
7
|
+
def initialize(&block)
|
8
|
+
instance_eval(&block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def check_error(&block)
|
12
|
+
@error_checker = block
|
13
|
+
end
|
14
|
+
|
15
|
+
def error_condition(&block)
|
16
|
+
@condition = block
|
17
|
+
end
|
18
|
+
|
19
|
+
def message(&block)
|
20
|
+
@message = block
|
21
|
+
end
|
22
|
+
|
23
|
+
def verify(feature)
|
24
|
+
if @error_checker
|
25
|
+
feature.instance_eval(&@error_checker)
|
26
|
+
else
|
27
|
+
default_error_check(feature)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def default_error_check(feature)
|
34
|
+
feature.instance_exec(@condition, @message) do |condition, message|
|
35
|
+
instance_eval(&condition) && error(instance_eval(&message))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RgGen
|
4
|
+
module Core
|
5
|
+
module InputBase
|
6
|
+
module YAMLLoader
|
7
|
+
private
|
8
|
+
|
9
|
+
def load_yaml(file)
|
10
|
+
yaml = File.binread(file)
|
11
|
+
result =
|
12
|
+
if Psych::VERSION >= '3.1.0'
|
13
|
+
YAML.safe_load(yaml, aliases: true, filename: file)
|
14
|
+
else
|
15
|
+
YAML.safe_load(yaml, [], [], true, file)
|
16
|
+
end
|
17
|
+
symbolize_key(result)
|
18
|
+
end
|
19
|
+
|
20
|
+
def symbolize_key(result)
|
21
|
+
case result
|
22
|
+
when Hash
|
23
|
+
result.keys.each do |key|
|
24
|
+
result[key.to_sym] = symbolize_key(result.delete(key))
|
25
|
+
end
|
26
|
+
when Array
|
27
|
+
result.map! { |value| symbolize_key(value) }
|
28
|
+
end
|
29
|
+
result
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RgGen
|
4
|
+
module Core
|
5
|
+
class Options
|
6
|
+
extend ::Forwardable
|
7
|
+
|
8
|
+
class Option
|
9
|
+
def initialize(option_name)
|
10
|
+
@option_name = option_name
|
11
|
+
block_given? && yield(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
def enable(parser, options)
|
15
|
+
options[@option_name] ||= default
|
16
|
+
parser.on(*args) { |value| handler(value, options, parser) }
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_setter :short_option
|
20
|
+
attr_setter :long_option
|
21
|
+
attr_setter :option_class
|
22
|
+
|
23
|
+
def default(value = nil, &block)
|
24
|
+
if block_given?
|
25
|
+
@default = block
|
26
|
+
elsif !value.nil?
|
27
|
+
@default = -> { value }
|
28
|
+
elsif @default
|
29
|
+
instance_exec(&@default)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def description(value = nil)
|
34
|
+
if value
|
35
|
+
@description = value
|
36
|
+
else
|
37
|
+
@description
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def action(&block)
|
42
|
+
if block_given?
|
43
|
+
@action = block
|
44
|
+
else
|
45
|
+
@action || default_action
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def args
|
52
|
+
[@short_option, @long_option, @option_class, description].compact
|
53
|
+
end
|
54
|
+
|
55
|
+
def default_action
|
56
|
+
proc do |value, options, option_name, _parser|
|
57
|
+
options[option_name] = value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def handler(value, options, parser)
|
62
|
+
instance_exec(value, options, @option_name, parser, &action)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.options
|
67
|
+
@options ||= {}
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.add_option(option_name, &body)
|
71
|
+
options[option_name] = Option.new(option_name, &body)
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize
|
75
|
+
@options = {}
|
76
|
+
end
|
77
|
+
|
78
|
+
attr_reader :original_args
|
79
|
+
attr_reader :register_map_files
|
80
|
+
|
81
|
+
def_delegator :@options, :[]
|
82
|
+
|
83
|
+
def parse(args)
|
84
|
+
@original_args = args
|
85
|
+
@register_map_files = option_parser.parse(args)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def option_parser
|
91
|
+
OptionParser.new do |parser|
|
92
|
+
parser.program_name = 'rggen'
|
93
|
+
parser.version = RgGen::Core::VERSION
|
94
|
+
parser.banner = 'Usage: rggen [options] register_map_files'
|
95
|
+
define_options(parser)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def define_options(parser)
|
100
|
+
self.class.options.each_value { |o| o.enable(parser, @options) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
Options.add_option(:setup) do |option|
|
105
|
+
option.long_option '--setup FILE'
|
106
|
+
option.default { default_steup_file }
|
107
|
+
option.description 'Specify a Ruby file to set up RgGen tool'
|
108
|
+
|
109
|
+
def option.default_steup_file
|
110
|
+
ENV['RGGEN_DEFAULT_SETUP_FILE'] || define_setup_file_from_const
|
111
|
+
end
|
112
|
+
|
113
|
+
def option.define_setup_file_from_const
|
114
|
+
require 'rggen/default_setup_file'
|
115
|
+
RgGen::DEFAULT_SETUP_FILE
|
116
|
+
rescue ::LoadError
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
Options.add_option(:configuration) do |option|
|
122
|
+
option.short_option '-c'
|
123
|
+
option.long_option '--configuration FILE'
|
124
|
+
option.default { ENV['RGGEN_DEFAULT_CONFIGURATION_FILE'] }
|
125
|
+
option.description 'Specify a configuration file'
|
126
|
+
end
|
127
|
+
|
128
|
+
Options.add_option(:output) do |option|
|
129
|
+
option.short_option '-o'
|
130
|
+
option.long_option '--output DIRECTORY'
|
131
|
+
option.default { '.' }
|
132
|
+
option.description 'Specify the directory where ' \
|
133
|
+
'generated file(s) will be written'
|
134
|
+
end
|
135
|
+
|
136
|
+
Options.add_option(:load_only) do |option|
|
137
|
+
option.long_option '--load-only'
|
138
|
+
option.default false
|
139
|
+
option.description 'Load setup, configuration and register map ' \
|
140
|
+
'files only; write no files'
|
141
|
+
end
|
142
|
+
|
143
|
+
Options.add_option(:enable) do |option|
|
144
|
+
option.long_option '--enable WRITER1[,WRITER2,...]'
|
145
|
+
option.option_class Array
|
146
|
+
option.default { [] }
|
147
|
+
option.action { |v, o, n| merge_enabled_writers(v, o, n) }
|
148
|
+
option.description 'Enable only the given writer(s) to write files'
|
149
|
+
|
150
|
+
def option.merge_enabled_writers(value, options, option_name)
|
151
|
+
options[option_name].concat(value.map(&:to_sym))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
Options.add_option(:version) do |option|
|
156
|
+
option.short_option '-v'
|
157
|
+
option.long_option '--version'
|
158
|
+
option.action do |_value, options|
|
159
|
+
options[:runner] = VersionPrinter.new(false)
|
160
|
+
end
|
161
|
+
option.description 'Display version'
|
162
|
+
end
|
163
|
+
|
164
|
+
Options.add_option(:verbose_version) do |option|
|
165
|
+
option.long_option '--verbose-version'
|
166
|
+
option.action do |_value, options|
|
167
|
+
options[:runner] = VersionPrinter.new(true)
|
168
|
+
end
|
169
|
+
option.description 'Load a setup Ruby file and display verbose version'
|
170
|
+
end
|
171
|
+
|
172
|
+
Options.add_option(:help) do |option|
|
173
|
+
option.short_option '-h'
|
174
|
+
option.long_option '--help'
|
175
|
+
option.action do |_value, options, _name, parser|
|
176
|
+
options[:runner] = HelpPrinter.new(parser)
|
177
|
+
end
|
178
|
+
option.description 'Display this message'
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RgGen
|
4
|
+
module Core
|
5
|
+
module OutputBase
|
6
|
+
class CodeGenerator
|
7
|
+
def register(kind, block)
|
8
|
+
return unless block
|
9
|
+
code_blocks[kind] << block
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate(context, kind, code)
|
13
|
+
return code unless generatable?(kind)
|
14
|
+
execute_code_blocks(
|
15
|
+
context, kind, code || context.create_blank_code
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def copy
|
20
|
+
generator = CodeGenerator.new
|
21
|
+
generator.copy_code_blocks(@code_blocks) if @code_blocks
|
22
|
+
generator
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def code_blocks
|
28
|
+
@code_blocks ||= Hash.new { |blocks, kind| blocks[kind] = [] }
|
29
|
+
end
|
30
|
+
|
31
|
+
def generatable?(kind)
|
32
|
+
@code_blocks&.key?(kind)
|
33
|
+
end
|
34
|
+
|
35
|
+
def execute_code_blocks(context, kind, code)
|
36
|
+
code_blocks[kind].each(&code_block_executor(context, code))
|
37
|
+
code
|
38
|
+
end
|
39
|
+
|
40
|
+
def code_block_executor(context, code)
|
41
|
+
lambda do |block|
|
42
|
+
if block.arity.zero?
|
43
|
+
code << context.instance_exec(&block)
|
44
|
+
else
|
45
|
+
context.instance_exec(code, &block)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def copy_code_blocks(original_blocks)
|
53
|
+
original_blocks
|
54
|
+
.each { |kind, blocks| code_blocks[kind] = blocks.dup }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RgGen
|
4
|
+
module Core
|
5
|
+
module OutputBase
|
6
|
+
class Component < Base::Component
|
7
|
+
include Base::HierarchicalAccessors
|
8
|
+
|
9
|
+
attr_reader :configuration
|
10
|
+
attr_reader :source
|
11
|
+
|
12
|
+
def post_initialize(_paren, configuration, source)
|
13
|
+
@configuration = configuration
|
14
|
+
@source = source
|
15
|
+
@need_children = source.need_children?
|
16
|
+
define_hierarchical_accessors
|
17
|
+
define_property_accessors
|
18
|
+
define_children_presense_indicator
|
19
|
+
end
|
20
|
+
|
21
|
+
def children?
|
22
|
+
!source.children.empty?
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_feature(feature)
|
26
|
+
super
|
27
|
+
import_feature_methods(feature, :class)
|
28
|
+
end
|
29
|
+
|
30
|
+
def pre_build
|
31
|
+
@features.each_value(&:pre_build)
|
32
|
+
end
|
33
|
+
|
34
|
+
def build
|
35
|
+
@features.each_value(&method(:build_feature))
|
36
|
+
@children.each(&:build)
|
37
|
+
end
|
38
|
+
|
39
|
+
def generate_code(kind, mode, code = nil)
|
40
|
+
code_generators(kind, mode).inject(code) { |c, g| g[c] }
|
41
|
+
end
|
42
|
+
|
43
|
+
def write_file(directory = nil)
|
44
|
+
@features.each_value { |feature| feature.write_file(directory) }
|
45
|
+
@children.each { |component| component.write_file(directory) }
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def define_property_accessors
|
51
|
+
def_delegators(:@source, *@source.properties)
|
52
|
+
end
|
53
|
+
|
54
|
+
INDICATOR_NAMES = {
|
55
|
+
register_map: :register_blocks?,
|
56
|
+
register_block: :registers?,
|
57
|
+
register: :bit_fields?
|
58
|
+
}.freeze
|
59
|
+
|
60
|
+
def define_children_presense_indicator
|
61
|
+
indicator_name = INDICATOR_NAMES[hierarchy]
|
62
|
+
indicator_name &&
|
63
|
+
singleton_exec { alias_method indicator_name, :children? }
|
64
|
+
end
|
65
|
+
|
66
|
+
def build_feature(feature)
|
67
|
+
feature.build
|
68
|
+
import_feature_methods(feature, :object)
|
69
|
+
end
|
70
|
+
|
71
|
+
def import_feature_methods(feature, scope)
|
72
|
+
receiver = "@features[:#{feature.feature_name}]"
|
73
|
+
methods = feature.exported_methods(scope)
|
74
|
+
def_delegators(receiver, *methods)
|
75
|
+
end
|
76
|
+
|
77
|
+
def code_generators(kind, mode)
|
78
|
+
[
|
79
|
+
[@features.each_value, [:pre_code, kind]],
|
80
|
+
*main_code_contexts(kind, mode),
|
81
|
+
[@features.each_value, [:post_code, kind]]
|
82
|
+
].map do |receivers, args|
|
83
|
+
lambda do |code|
|
84
|
+
receivers.inject(code) { |c, r| r.generate_code(*args, c) }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def main_code_contexts(kind, mode)
|
90
|
+
contexts = [
|
91
|
+
[@features.each_value, [:main_code, kind]],
|
92
|
+
[@children, [kind, mode]]
|
93
|
+
]
|
94
|
+
contexts.reverse! if mode == :bottom_up
|
95
|
+
contexts
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|