d-mark 1.0.0a1 → 1.0.0b2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Gemfile.lock +61 -58
- data/NEWS.md +36 -0
- data/README.md +20 -0
- data/Rakefile +3 -3
- data/d-mark.gemspec +3 -5
- data/ideas.dmark +17 -0
- data/lib/d-mark/parser.rb +56 -71
- data/lib/d-mark/translator.rb +42 -13
- data/lib/d-mark/version.rb +1 -1
- data/samples/trivial.dmark +1 -1
- data/spec/d-mark/parser_spec.rb +150 -48
- data/spec/d-mark/translator_spec.rb +74 -18
- metadata +12 -35
- data/README.adoc +0 -221
- data/lib/d-mark/cli.rb +0 -28
- data/samples/identifiers-and-patterns.dmark +0 -539
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5108217b3f4db3e0f50eaf6613a7ac7be6fc1df7ed910167c014593c2e05f74c
|
4
|
+
data.tar.gz: 1fcfc2341f5f3b49d903e52a2c743b02fbaece7d4415a5d2e72db4bac90ccb09
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4d5238ced88d951e8edf2b6323a4ff698a24c70021265353d3f778b854b235f76aad85d872bce2c8bcb7683b4078f7a0b839e21f39380e9fe1583a7cebcb2e9
|
7
|
+
data.tar.gz: 41f4a62ed8cf1d86da8e0b5615f0e5ff485e3d314a5590e518e6ca5d32984b9ba792484bd60be2654a057ef68df4512b3de9ba9a4f2fa489b92651d72bc9e517
|
data/Gemfile.lock
CHANGED
@@ -1,25 +1,24 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
d-mark (1.0.
|
4
|
+
d-mark (1.0.0b2)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
-
ast (2.
|
10
|
-
codecov (0.
|
9
|
+
ast (2.4.1)
|
10
|
+
codecov (0.2.12)
|
11
11
|
json
|
12
12
|
simplecov
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
ffi (1.9.10)
|
13
|
+
coderay (1.1.3)
|
14
|
+
diff-lcs (1.4.4)
|
15
|
+
docile (1.3.4)
|
16
|
+
ffi (1.14.2)
|
18
17
|
formatador (0.2.5)
|
19
|
-
guard (2.
|
18
|
+
guard (2.16.2)
|
20
19
|
formatador (>= 0.2.4)
|
21
|
-
listen (>= 2.7,
|
22
|
-
lumberjack (
|
20
|
+
listen (>= 2.7, < 4.0)
|
21
|
+
lumberjack (>= 1.0.12, < 2.0)
|
23
22
|
nenv (~> 0.1)
|
24
23
|
notiffany (~> 0.0)
|
25
24
|
pry (>= 0.9.12)
|
@@ -28,64 +27,68 @@ GEM
|
|
28
27
|
guard-rake (1.0.0)
|
29
28
|
guard
|
30
29
|
rake
|
31
|
-
json (
|
32
|
-
listen (3.0
|
33
|
-
rb-fsevent (>= 0.
|
34
|
-
rb-inotify (>= 0.9.
|
35
|
-
lumberjack (1.
|
36
|
-
method_source (0.
|
30
|
+
json (2.5.1)
|
31
|
+
listen (3.4.0)
|
32
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
33
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
34
|
+
lumberjack (1.2.8)
|
35
|
+
method_source (1.0.0)
|
37
36
|
nenv (0.3.0)
|
38
|
-
notiffany (0.
|
37
|
+
notiffany (0.1.3)
|
39
38
|
nenv (~> 0.1)
|
40
39
|
shellany (~> 0.0)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
pry (0.
|
45
|
-
coderay (~> 1.1
|
46
|
-
method_source (~> 0
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
rb-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
rspec-
|
56
|
-
rspec-
|
57
|
-
|
58
|
-
|
59
|
-
|
40
|
+
parallel (1.20.1)
|
41
|
+
parser (3.0.0.0)
|
42
|
+
ast (~> 2.4.1)
|
43
|
+
pry (0.13.1)
|
44
|
+
coderay (~> 1.1)
|
45
|
+
method_source (~> 1.0)
|
46
|
+
rainbow (3.0.0)
|
47
|
+
rake (13.0.3)
|
48
|
+
rb-fsevent (0.10.4)
|
49
|
+
rb-inotify (0.10.1)
|
50
|
+
ffi (~> 1.0)
|
51
|
+
regexp_parser (2.0.3)
|
52
|
+
rexml (3.2.4)
|
53
|
+
rspec (3.10.0)
|
54
|
+
rspec-core (~> 3.10.0)
|
55
|
+
rspec-expectations (~> 3.10.0)
|
56
|
+
rspec-mocks (~> 3.10.0)
|
57
|
+
rspec-core (3.10.1)
|
58
|
+
rspec-support (~> 3.10.0)
|
59
|
+
rspec-expectations (3.10.1)
|
60
60
|
diff-lcs (>= 1.2.0, < 2.0)
|
61
|
-
rspec-support (~> 3.
|
62
|
-
rspec-mocks (3.
|
61
|
+
rspec-support (~> 3.10.0)
|
62
|
+
rspec-mocks (3.10.1)
|
63
63
|
diff-lcs (>= 1.2.0, < 2.0)
|
64
|
-
rspec-support (~> 3.
|
65
|
-
rspec-support (3.
|
66
|
-
rubocop (
|
67
|
-
|
68
|
-
|
69
|
-
rainbow (>=
|
64
|
+
rspec-support (~> 3.10.0)
|
65
|
+
rspec-support (3.10.1)
|
66
|
+
rubocop (1.7.0)
|
67
|
+
parallel (~> 1.10)
|
68
|
+
parser (>= 2.7.1.5)
|
69
|
+
rainbow (>= 2.2.2, < 4.0)
|
70
|
+
regexp_parser (>= 1.8, < 3.0)
|
71
|
+
rexml
|
72
|
+
rubocop-ast (>= 1.2.0, < 2.0)
|
70
73
|
ruby-progressbar (~> 1.7)
|
71
|
-
unicode-display_width (
|
72
|
-
|
74
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
75
|
+
rubocop-ast (1.3.0)
|
76
|
+
parser (>= 2.7.1.5)
|
77
|
+
ruby-progressbar (1.11.0)
|
73
78
|
shellany (0.0.1)
|
74
|
-
simplecov (0.
|
75
|
-
docile (~> 1.1
|
76
|
-
|
77
|
-
|
78
|
-
simplecov-html (0.
|
79
|
-
|
80
|
-
thor (0.
|
81
|
-
unicode-display_width (
|
82
|
-
url (0.3.2)
|
79
|
+
simplecov (0.20.0)
|
80
|
+
docile (~> 1.1)
|
81
|
+
simplecov-html (~> 0.11)
|
82
|
+
simplecov_json_formatter (~> 0.1)
|
83
|
+
simplecov-html (0.12.3)
|
84
|
+
simplecov_json_formatter (0.1.2)
|
85
|
+
thor (1.0.1)
|
86
|
+
unicode-display_width (1.7.0)
|
83
87
|
|
84
88
|
PLATFORMS
|
85
89
|
ruby
|
86
90
|
|
87
91
|
DEPENDENCIES
|
88
|
-
bundler (>= 1.11.2, < 2.0)
|
89
92
|
codecov
|
90
93
|
d-mark!
|
91
94
|
guard
|
@@ -94,4 +97,4 @@ DEPENDENCIES
|
|
94
97
|
rubocop
|
95
98
|
|
96
99
|
BUNDLED WITH
|
97
|
-
1.
|
100
|
+
1.17.3
|
data/NEWS.md
CHANGED
@@ -1,5 +1,41 @@
|
|
1
1
|
# D★Mark news
|
2
2
|
|
3
|
+
## 1.0.0b2 (2021-01-01)
|
4
|
+
|
5
|
+
Enhancements:
|
6
|
+
|
7
|
+
* Added support for Ruby 3.x
|
8
|
+
|
9
|
+
## 1.0.0b1 (2018-02-26)
|
10
|
+
|
11
|
+
Enhancements:
|
12
|
+
|
13
|
+
* Added `ParserError#fancy_message`
|
14
|
+
|
15
|
+
## 1.0.0a4 (2017-08-19)
|
16
|
+
|
17
|
+
Changes:
|
18
|
+
|
19
|
+
* Relaxed identifier restrictions (#16, #17)
|
20
|
+
|
21
|
+
Enhancements:
|
22
|
+
|
23
|
+
* Made various speed improvements
|
24
|
+
|
25
|
+
## 1.0.0a3 (2016-07-03)
|
26
|
+
|
27
|
+
Changes:
|
28
|
+
|
29
|
+
* Dropped support for Ruby 2.1
|
30
|
+
* Removed CLI
|
31
|
+
* Changed `Translator` API to allow specialisation
|
32
|
+
|
33
|
+
## 1.0.0a2 (2016-03-06)
|
34
|
+
|
35
|
+
Changes:
|
36
|
+
|
37
|
+
* Changed block form from `elem. content` to `#elem content` (#6)
|
38
|
+
|
3
39
|
## 1.0.0a1 (2016-02-20)
|
4
40
|
|
5
41
|
Fixes:
|
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# D★Mark
|
2
|
+
|
3
|
+
[![Gem version](http://img.shields.io/gem/v/d-mark.svg)](http://rubygems.org/gems/d-mark)
|
4
|
+
[![Build status](http://img.shields.io/travis/ddfreyne/d-mark.svg)](https://travis-ci.org/ddfreyne/d-mark)
|
5
|
+
[![Code Climate](http://img.shields.io/codeclimate/github/ddfreyne/d-mark.svg)](https://codeclimate.com/github/ddfreyne/d-mark)
|
6
|
+
[![Code Coverage](http://img.shields.io/codecov/c/github/ddfreyne/d-mark.svg)](https://codecov.io/github/ddfreyne/d-mark)
|
7
|
+
|
8
|
+
_D★Mark_ is a language for marking up prose. It facilitates writing semantically meaningful text, without limiting itself to the semantics provided by HTML or Markdown.
|
9
|
+
|
10
|
+
If you’re a technical writer looking for a flexible markup language, D★Mark might be a good fit.
|
11
|
+
|
12
|
+
Here’s an example of D★Mark:
|
13
|
+
|
14
|
+
```
|
15
|
+
#para This a paragraph; an element in block form containing some text.
|
16
|
+
|
17
|
+
#note[only=web] This is a note that will %em{only} show up on web.
|
18
|
+
```
|
19
|
+
|
20
|
+
For details, see the [D★Mark web page](http://ddfreyne.github.io/d-mark/).
|
data/Rakefile
CHANGED
@@ -7,8 +7,8 @@ RSpec::Core::RakeTask.new(:spec) do |t|
|
|
7
7
|
end
|
8
8
|
|
9
9
|
RuboCop::RakeTask.new(:rubocop) do |task|
|
10
|
-
task.options = %w
|
11
|
-
task.patterns = ['lib/**/*.rb', 'spec/**/*.rb']
|
10
|
+
task.options = %w[--display-cop-names --format simple]
|
11
|
+
task.patterns = ['lib/**/*.rb', 'spec/**/*.rb', 'Gemfile', '*.gemspec', 'Rakefile']
|
12
12
|
end
|
13
13
|
|
14
|
-
task default: [
|
14
|
+
task default: %i[spec rubocop]
|
data/d-mark.gemspec
CHANGED
@@ -17,10 +17,8 @@ Gem::Specification.new do |s|
|
|
17
17
|
['d-mark.gemspec']
|
18
18
|
s.require_paths = ['lib']
|
19
19
|
|
20
|
-
s.rdoc_options = ['--main', 'README.
|
21
|
-
s.extra_rdoc_files = ['LICENSE', 'README.
|
20
|
+
s.rdoc_options = ['--main', 'README.md']
|
21
|
+
s.extra_rdoc_files = ['LICENSE', 'README.md', 'NEWS.md']
|
22
22
|
|
23
|
-
s.required_ruby_version = '>= 2.
|
24
|
-
|
25
|
-
s.add_development_dependency('bundler', '>= 1.11.2', '< 2.0')
|
23
|
+
s.required_ruby_version = '>= 2.5'
|
26
24
|
end
|
data/ideas.dmark
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#h1 Ideas
|
2
|
+
|
3
|
+
#ul
|
4
|
+
#li
|
5
|
+
#p Support validation. Which elements can contain which other elements? Which elements can live at the root?
|
6
|
+
|
7
|
+
#li
|
8
|
+
#p Have different translators built-in. I’m thinking %code{BasicMarkdown2HTML} and %code{HTMLForWriters2HTML}, and perhaps %code{DocBook2HTML}.
|
9
|
+
|
10
|
+
#li
|
11
|
+
#p A refactored parser that does not need to reset %code{@pos}, and maybe not even have any kind of mutable state at all.
|
12
|
+
|
13
|
+
#li
|
14
|
+
#p Fancy exception handling actually used somewhere.
|
15
|
+
|
16
|
+
#li
|
17
|
+
#p Documentation for every exception that occurs.
|
data/lib/d-mark/parser.rb
CHANGED
@@ -4,20 +4,35 @@ module DMark
|
|
4
4
|
attr_reader :line_nr
|
5
5
|
attr_reader :col_nr
|
6
6
|
|
7
|
-
def initialize(line_nr, col_nr, msg)
|
7
|
+
def initialize(line_nr, col_nr, msg, content)
|
8
8
|
@line_nr = line_nr
|
9
9
|
@col_nr = col_nr
|
10
10
|
@msg = msg
|
11
|
+
@content = content
|
11
12
|
|
12
13
|
super("parse error at line #{@line_nr + 1}, col #{@col_nr + 1}: #{@msg}")
|
13
14
|
end
|
15
|
+
|
16
|
+
def fancy_message
|
17
|
+
line = @content.lines[line_nr]
|
18
|
+
|
19
|
+
lines = [
|
20
|
+
message,
|
21
|
+
'',
|
22
|
+
line.rstrip,
|
23
|
+
"\e[31m" + ' ' * [col_nr, 0].max + '↑' + "\e[0m"
|
24
|
+
]
|
25
|
+
|
26
|
+
lines.join("\n")
|
27
|
+
end
|
14
28
|
end
|
15
29
|
|
16
30
|
attr_reader :pos
|
17
31
|
|
18
32
|
def initialize(input)
|
19
33
|
@input = input
|
20
|
-
@input_chars =
|
34
|
+
@input_chars = input.chars
|
35
|
+
@length = @input_chars.size
|
21
36
|
|
22
37
|
@pos = 0
|
23
38
|
@col_nr = 0
|
@@ -40,6 +55,7 @@ module DMark
|
|
40
55
|
|
41
56
|
loop do
|
42
57
|
break if eof?
|
58
|
+
|
43
59
|
res << read_block_with_children
|
44
60
|
end
|
45
61
|
|
@@ -48,20 +64,12 @@ module DMark
|
|
48
64
|
|
49
65
|
##########
|
50
66
|
|
51
|
-
def peek_char(pos = @pos)
|
52
|
-
if eof?
|
53
|
-
nil
|
54
|
-
else
|
55
|
-
@input_chars[pos]
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
67
|
def eof?(pos = @pos)
|
60
|
-
pos >= @
|
68
|
+
pos >= @length
|
61
69
|
end
|
62
70
|
|
63
71
|
def advance
|
64
|
-
if
|
72
|
+
if @input_chars[@pos] == "\n"
|
65
73
|
@line_nr += 1
|
66
74
|
@col_nr = 0
|
67
75
|
end
|
@@ -70,10 +78,10 @@ module DMark
|
|
70
78
|
@col_nr += 1
|
71
79
|
end
|
72
80
|
|
73
|
-
def read_char(
|
74
|
-
char =
|
75
|
-
if char !=
|
76
|
-
raise_parse_error("expected #{
|
81
|
+
def read_char(expected_char)
|
82
|
+
char = @input_chars[@pos]
|
83
|
+
if char != expected_char
|
84
|
+
raise_parse_error("expected #{expected_char.inspect}, but got #{char.nil? ? 'EOF' : char.inspect}")
|
77
85
|
else
|
78
86
|
advance
|
79
87
|
char
|
@@ -118,7 +126,7 @@ module DMark
|
|
118
126
|
pos = @pos
|
119
127
|
|
120
128
|
loop do
|
121
|
-
case
|
129
|
+
case @input_chars[pos]
|
122
130
|
when ' '
|
123
131
|
pos += 1
|
124
132
|
when nil
|
@@ -133,32 +141,11 @@ module DMark
|
|
133
141
|
|
134
142
|
# FIXME: ugly and duplicated
|
135
143
|
def try_read_block_start
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
case peek_char
|
143
|
-
when '['
|
144
|
-
true
|
145
|
-
when '.'
|
146
|
-
advance
|
147
|
-
[' ', "\n", nil].include?(peek_char)
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
@pos = old_pos
|
152
|
-
success
|
153
|
-
end
|
154
|
-
|
155
|
-
# FIXME: ugly and duplicated
|
156
|
-
def try_read_identifier_head
|
157
|
-
char = peek_char
|
158
|
-
case char
|
159
|
-
when 'a'..'z'
|
160
|
-
advance
|
161
|
-
char
|
144
|
+
if @input_chars[@pos] == '#'
|
145
|
+
next_char = @input_chars[@pos + 1]
|
146
|
+
('a'..'z').cover?(next_char)
|
147
|
+
else
|
148
|
+
false
|
162
149
|
end
|
163
150
|
end
|
164
151
|
|
@@ -167,7 +154,7 @@ module DMark
|
|
167
154
|
pos = @pos
|
168
155
|
|
169
156
|
loop do
|
170
|
-
case
|
157
|
+
case @input_chars[pos]
|
171
158
|
when ' '
|
172
159
|
pos += 1
|
173
160
|
indentation_chars += 1
|
@@ -187,18 +174,17 @@ module DMark
|
|
187
174
|
end
|
188
175
|
|
189
176
|
def read_single_block
|
177
|
+
read_char('#')
|
190
178
|
identifier = read_identifier
|
191
179
|
|
192
180
|
attributes =
|
193
|
-
if
|
181
|
+
if @input_chars[@pos] == '['
|
194
182
|
read_attributes
|
195
183
|
else
|
196
184
|
{}
|
197
185
|
end
|
198
186
|
|
199
|
-
|
200
|
-
|
201
|
-
case peek_char
|
187
|
+
case @input_chars[@pos]
|
202
188
|
when nil, "\n"
|
203
189
|
advance
|
204
190
|
ElementNode.new(identifier, attributes, [])
|
@@ -211,7 +197,7 @@ module DMark
|
|
211
197
|
end
|
212
198
|
|
213
199
|
def read_end_of_inline_content
|
214
|
-
char =
|
200
|
+
char = @input_chars[@pos]
|
215
201
|
case char
|
216
202
|
when "\n", nil
|
217
203
|
advance
|
@@ -229,9 +215,9 @@ module DMark
|
|
229
215
|
end
|
230
216
|
|
231
217
|
def read_identifier_head
|
232
|
-
char =
|
218
|
+
char = @input_chars[@pos]
|
233
219
|
case char
|
234
|
-
when 'a'..'z'
|
220
|
+
when 'a'..'z', 'A'..'Z'
|
235
221
|
advance
|
236
222
|
char
|
237
223
|
else
|
@@ -239,18 +225,17 @@ module DMark
|
|
239
225
|
end
|
240
226
|
end
|
241
227
|
|
228
|
+
IDENTIFIER_CHARS = Set.new(['a'..'z', 'A'..'Z', ['-', '_'], '0'..'9'].map(&:to_a).flatten)
|
229
|
+
|
242
230
|
def read_identifier_tail
|
243
231
|
res = ''
|
244
232
|
|
245
233
|
loop do
|
246
|
-
char =
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
else
|
252
|
-
break
|
253
|
-
end
|
234
|
+
char = @input_chars[@pos]
|
235
|
+
break unless IDENTIFIER_CHARS.include?(char)
|
236
|
+
|
237
|
+
advance
|
238
|
+
res << char
|
254
239
|
end
|
255
240
|
|
256
241
|
res.to_s
|
@@ -263,7 +248,7 @@ module DMark
|
|
263
248
|
|
264
249
|
at_start = true
|
265
250
|
loop do
|
266
|
-
char =
|
251
|
+
char = @input_chars[@pos]
|
267
252
|
case char
|
268
253
|
when ']'
|
269
254
|
advance
|
@@ -272,7 +257,7 @@ module DMark
|
|
272
257
|
read_char(',') unless at_start
|
273
258
|
|
274
259
|
key = read_attribute_key
|
275
|
-
if
|
260
|
+
if @input_chars[@pos] == '='
|
276
261
|
read_char('=')
|
277
262
|
value = read_attribute_value
|
278
263
|
else
|
@@ -297,7 +282,7 @@ module DMark
|
|
297
282
|
|
298
283
|
is_escaping = false
|
299
284
|
loop do
|
300
|
-
char =
|
285
|
+
char = @input_chars[@pos]
|
301
286
|
|
302
287
|
if is_escaping
|
303
288
|
case char
|
@@ -337,7 +322,7 @@ module DMark
|
|
337
322
|
res = []
|
338
323
|
|
339
324
|
loop do
|
340
|
-
char =
|
325
|
+
char = @input_chars[@pos]
|
341
326
|
case char
|
342
327
|
when "\n", nil
|
343
328
|
break
|
@@ -358,7 +343,7 @@ module DMark
|
|
358
343
|
res = ''
|
359
344
|
|
360
345
|
loop do
|
361
|
-
char =
|
346
|
+
char = @input_chars[@pos]
|
362
347
|
case char
|
363
348
|
when nil, "\n", '%', '}'
|
364
349
|
break
|
@@ -368,17 +353,17 @@ module DMark
|
|
368
353
|
end
|
369
354
|
end
|
370
355
|
|
371
|
-
res
|
356
|
+
res
|
372
357
|
end
|
373
358
|
|
374
359
|
def read_percent_body
|
375
|
-
char =
|
360
|
+
char = @input_chars[@pos]
|
376
361
|
case char
|
377
|
-
when '%', '}'
|
362
|
+
when '%', '}', '#'
|
378
363
|
advance
|
379
|
-
char
|
364
|
+
char
|
380
365
|
when nil, "\n"
|
381
|
-
raise_parse_error(
|
366
|
+
raise_parse_error('expected something after %')
|
382
367
|
else
|
383
368
|
read_inline_element
|
384
369
|
end
|
@@ -387,7 +372,7 @@ module DMark
|
|
387
372
|
def read_inline_element
|
388
373
|
name = read_identifier
|
389
374
|
attributes =
|
390
|
-
if
|
375
|
+
if @input_chars[@pos] == '['
|
391
376
|
read_attributes
|
392
377
|
else
|
393
378
|
{}
|
@@ -400,7 +385,7 @@ module DMark
|
|
400
385
|
end
|
401
386
|
|
402
387
|
def raise_parse_error(msg)
|
403
|
-
raise ParserError.new(@line_nr, @col_nr, msg)
|
388
|
+
raise ParserError.new(@line_nr, @col_nr, msg, @input)
|
404
389
|
end
|
405
390
|
end
|
406
391
|
end
|