retter 0.2.0 → 0.2.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.
- data/ChangeLog.md +53 -0
- data/README.md +39 -7
- data/bin/retter +1 -1
- data/lib/retter/command.rb +106 -97
- data/lib/retter/config.rb +54 -76
- data/lib/retter/configurable.rb +26 -0
- data/lib/retter/entries.rb +28 -26
- data/lib/retter/entry.rb +76 -74
- data/lib/retter/generator/skel/Retterfile +26 -17
- data/lib/retter/page/view_helper.rb +5 -5
- data/lib/retter/page.rb +8 -6
- data/lib/retter/pages/article.rb +37 -35
- data/lib/retter/pages/entries.rb +15 -0
- data/lib/retter/pages/entry.rb +25 -22
- data/lib/retter/pages/feed.rb +35 -33
- data/lib/retter/pages/index.rb +9 -7
- data/lib/retter/pages/profile.rb +9 -7
- data/lib/retter/pages.rb +50 -13
- data/lib/retter/preprint.rb +5 -5
- data/lib/retter/renderers.rb +0 -1
- data/lib/retter/repository.rb +5 -4
- data/lib/retter/version.rb +1 -1
- data/lib/retter.rb +20 -24
- data/retter.gemspec +3 -1
- data/spec/bin/fake_editor +3 -0
- data/spec/command/callback_spec.rb +1 -1
- data/spec/command/commit_spec.rb +2 -2
- data/spec/command/edit_spec.rb +9 -7
- data/spec/command/invoke_after_spec.rb +2 -2
- data/spec/command/list_spec.rb +4 -2
- data/spec/command/open_spec.rb +1 -1
- data/spec/command/preview_spec.rb +2 -2
- data/spec/command/rebind_spec.rb +50 -10
- data/spec/spec_helper.rb +11 -6
- data/spec/support/example_group_helper.rb +1 -9
- metadata +68 -43
- data/CHANGELOG.md +0 -21
- data/lib/retter/pages/archive.rb +0 -13
data/lib/retter/entries.rb
CHANGED
@@ -1,17 +1,25 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
require 'active_support/
|
3
|
+
require 'active_support/cache'
|
4
4
|
require 'digest/sha1'
|
5
5
|
require 'redcarpet'
|
6
|
+
require 'chronic'
|
6
7
|
|
7
8
|
module Retter
|
8
9
|
class EntryLoadError < RetterError; end
|
9
10
|
|
10
11
|
class Entries < Array
|
11
|
-
include
|
12
|
+
include Stationery
|
13
|
+
extend Configurable
|
14
|
+
|
15
|
+
configurable :renderer, :retters_dir, :wip_file
|
12
16
|
|
13
17
|
def initialize
|
14
|
-
load_entries
|
18
|
+
load_entries retters_dir
|
19
|
+
end
|
20
|
+
|
21
|
+
def retter_file(date)
|
22
|
+
retters_dir.join(date ? date.strftime('%Y%m%d.md') : 'today.md')
|
15
23
|
end
|
16
24
|
|
17
25
|
def detect_by_string(str)
|
@@ -36,10 +44,10 @@ module Retter
|
|
36
44
|
|
37
45
|
def detect_by_filename(filename)
|
38
46
|
case filename
|
39
|
-
when
|
47
|
+
when wip_file.basename.to_path
|
40
48
|
wip_entry
|
41
49
|
else
|
42
|
-
detect {|e| e.pathname.basename.
|
50
|
+
detect {|e| e.pathname.basename.to_path == filename }
|
43
51
|
end
|
44
52
|
end
|
45
53
|
|
@@ -52,43 +60,37 @@ module Retter
|
|
52
60
|
end
|
53
61
|
|
54
62
|
def parse_date_string(date_str)
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
when /^tomorrow$/i then 1.day.since
|
59
|
-
when /^[0-9]+[\.\s](?:days?|weeks?|months?|years?)[\.\s](?:ago|since)$/i
|
60
|
-
eval(date_str.gsub(/\s+/, '.')).to_date
|
61
|
-
else
|
62
|
-
Date.parse(date_str)
|
63
|
-
end
|
63
|
+
normalized = date_str.gsub(/\./, ' ')
|
64
|
+
|
65
|
+
(Chronic.parse(normalized) || Date.parse(normalized)).to_date
|
64
66
|
end
|
65
67
|
|
66
68
|
def wip_entry(date = nil)
|
67
|
-
|
68
|
-
|
69
|
-
|
69
|
+
file = retter_file(date)
|
70
|
+
date = date || Date.today
|
71
|
+
body = file.exist? ? file.read : ''
|
70
72
|
|
71
|
-
|
73
|
+
Entry.new date: date, body: rendered_body(body), pathname: file
|
72
74
|
end
|
73
75
|
|
74
76
|
def commit_wip_entry!
|
75
|
-
if
|
76
|
-
copy =
|
77
|
-
|
78
|
-
|
77
|
+
if wip_file.exist?
|
78
|
+
copy = wip_file.read
|
79
|
+
retter_file(Date.today).open('a') {|f| f.puts copy }
|
80
|
+
wip_file.unlink
|
79
81
|
end
|
80
82
|
|
81
|
-
Retter.
|
83
|
+
Retter.reset!
|
82
84
|
end
|
83
85
|
|
84
86
|
def load_entries(path)
|
85
87
|
date_files = find_markup_files(path).map {|file|
|
86
|
-
date_str = file.basename('.*').
|
88
|
+
date_str = file.basename('.*').to_path
|
87
89
|
[Date.parse(date_str), file]
|
88
90
|
}.sort_by(&:first)
|
89
91
|
|
90
92
|
date_files.reverse_each {|date, file|
|
91
|
-
self <<
|
93
|
+
self << Entry.new(date: date, body: rendered_body(file.read))
|
92
94
|
}
|
93
95
|
end
|
94
96
|
|
@@ -102,7 +104,7 @@ module Retter
|
|
102
104
|
|
103
105
|
config.cache.fetch(key) do
|
104
106
|
Redcarpet::Markdown.new(
|
105
|
-
|
107
|
+
renderer,
|
106
108
|
autolink: true,
|
107
109
|
space_after_headers: true,
|
108
110
|
fenced_code_blocks: true,
|
data/lib/retter/entry.rb
CHANGED
@@ -2,103 +2,105 @@
|
|
2
2
|
|
3
3
|
require 'nokogiri'
|
4
4
|
|
5
|
-
|
6
|
-
class
|
7
|
-
|
5
|
+
module Retter
|
6
|
+
class Entry
|
7
|
+
class Article
|
8
|
+
attr_accessor :entry, :id, :title, :body
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
def initialize(attrs = {})
|
11
|
+
@id, @entry, @title, @body = attrs.values_at(:id, :entry, :title, :body)
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
def to_s
|
15
|
+
body
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
def next
|
19
|
+
articles[index.next] || (entry.next && entry.next.articles.first)
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
22
|
+
def prev
|
23
|
+
index.pred < 0 ? (entry.prev && entry.prev.articles.last) : articles[index.pred]
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
def index
|
27
|
+
articles.index(self)
|
28
|
+
end
|
28
29
|
|
29
|
-
|
30
|
-
|
30
|
+
def articles
|
31
|
+
@articles ||= entry.articles
|
32
|
+
end
|
31
33
|
end
|
32
|
-
end
|
33
34
|
|
34
|
-
|
35
|
+
include Stationery
|
35
36
|
|
36
|
-
|
37
|
-
|
37
|
+
attr_accessor :date, :lede, :body, :articles
|
38
|
+
attr_reader :pathname
|
38
39
|
|
39
|
-
|
40
|
-
|
40
|
+
def initialize(attrs={})
|
41
|
+
@date, @body = attrs.values_at(:date, :body)
|
41
42
|
|
42
|
-
|
43
|
-
|
43
|
+
pathname_by_date = Entries.retters_dir.join(date.strftime('%Y%m%d.md'))
|
44
|
+
@pathname = attrs[:pathname] || pathname_by_date
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
def path
|
51
|
-
pathname.to_s
|
52
|
-
end
|
46
|
+
attach_titles
|
47
|
+
extract_articles
|
48
|
+
load_lede
|
49
|
+
end
|
53
50
|
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
def path
|
52
|
+
pathname.to_path
|
53
|
+
end
|
57
54
|
|
58
|
-
|
59
|
-
|
60
|
-
|
55
|
+
def to_s
|
56
|
+
body
|
57
|
+
end
|
61
58
|
|
62
|
-
|
63
|
-
|
64
|
-
|
59
|
+
def next
|
60
|
+
entries[index.next]
|
61
|
+
end
|
65
62
|
|
66
|
-
|
67
|
-
|
68
|
-
|
63
|
+
def prev
|
64
|
+
entries[index.pred] unless index.pred < 0
|
65
|
+
end
|
69
66
|
|
70
|
-
|
67
|
+
def index
|
68
|
+
entries.index(self) || 0
|
69
|
+
end
|
71
70
|
|
72
|
-
|
73
|
-
Nokogiri::HTML(body)
|
74
|
-
end
|
71
|
+
private
|
75
72
|
|
76
|
-
|
77
|
-
|
78
|
-
html.search('//h1').each_with_index do |h1, seq|
|
79
|
-
h1.set_attribute 'id', "a#{seq}"
|
73
|
+
def body_elements
|
74
|
+
Nokogiri::HTML(body)
|
80
75
|
end
|
81
76
|
|
82
|
-
|
83
|
-
|
77
|
+
def attach_titles
|
78
|
+
html = body_elements
|
79
|
+
html.search('//h1').each_with_index do |h1, seq|
|
80
|
+
h1.set_attribute 'id', "a#{seq}"
|
81
|
+
end
|
84
82
|
|
85
|
-
|
86
|
-
|
87
|
-
if c.name == 'h1'
|
88
|
-
r << Article.new(entry: self, id: c.attr('id'), title: c.text, body: '')
|
89
|
-
else
|
90
|
-
next if r.empty?
|
83
|
+
@body = html.search('//body/*').to_s
|
84
|
+
end
|
91
85
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
86
|
+
def extract_articles
|
87
|
+
@articles = body_elements.search('body > *').each_with_object([]) {|c, r|
|
88
|
+
if c.name == 'h1'
|
89
|
+
r << Article.new(entry: self, id: c.attr('id'), title: c.text, body: '')
|
90
|
+
else
|
91
|
+
next if r.empty?
|
92
|
+
|
93
|
+
article = r.last
|
94
|
+
article.body += c.to_s
|
95
|
+
end
|
96
|
+
} || []
|
97
|
+
end
|
97
98
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
99
|
+
def load_lede
|
100
|
+
@lede = body_elements.search('body > *').each_with_object('') {|c, r|
|
101
|
+
break r if c.name == 'h1'
|
102
|
+
r << c.to_s
|
103
|
+
}
|
104
|
+
end
|
103
105
|
end
|
104
106
|
end
|
@@ -7,23 +7,32 @@ description '<%= name %>'
|
|
7
7
|
author '<%= ENV["USER"] %>'
|
8
8
|
renderer Retter::Renderers::PygmentsRenderer
|
9
9
|
|
10
|
-
|
11
|
-
##
|
12
|
-
## Syntax:
|
13
|
-
## after [command], [invoke command or proc]
|
10
|
+
# Callbacks for retter sub-command: edit, rebind, commit
|
14
11
|
#
|
15
|
-
|
16
|
-
#
|
12
|
+
# Syntax:
|
13
|
+
# after [command], [invoke command or proc]
|
17
14
|
#
|
18
|
-
|
19
|
-
#
|
20
|
-
# if yes?("Deploy now? [yes/no]")
|
21
|
-
# system "cd #{config.retter_home}"
|
22
|
-
# system 'git push origin master'
|
23
|
-
# end
|
24
|
-
# end
|
15
|
+
# Using symbol example:
|
16
|
+
# after :rebind, :commit
|
25
17
|
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
18
|
+
# Using proc example:
|
19
|
+
# after :commit do
|
20
|
+
# if yes?("Deploy now? [yes/no]")
|
21
|
+
# system "cd #{config.retter_home}"
|
22
|
+
# system 'git push origin master'
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# after :edit do
|
27
|
+
# Retter.reset!
|
28
|
+
# ident = ARGV.pop || 'today'
|
29
|
+
#
|
30
|
+
# preview ident if yes?("Preview now? [yes/no]")
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# Skip binding:
|
34
|
+
# examples:
|
35
|
+
# allow_binding :none
|
36
|
+
# allow_binding [:profile, :entries, :feed]
|
37
|
+
#
|
38
|
+
# Anyway index page will be bound.
|
@@ -1,14 +1,14 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
module Retter
|
4
|
-
module ViewHelper
|
5
|
-
include
|
3
|
+
module Retter
|
4
|
+
module Page::ViewHelper
|
5
|
+
include Stationery
|
6
6
|
|
7
7
|
def entry_path(*args)
|
8
8
|
case args.first
|
9
9
|
when Date, Time
|
10
10
|
entry_path_by_date(*args)
|
11
|
-
when
|
11
|
+
when Entry
|
12
12
|
entry_path_by_entry(args.first)
|
13
13
|
else
|
14
14
|
raise TypeError, "wrong argument type #{args.first.class} (expected Date, Time or Retter::Entry)"
|
@@ -19,7 +19,7 @@ module Retter::Page
|
|
19
19
|
case args.first
|
20
20
|
when Date, Time
|
21
21
|
article_path_by_date_and_id(*args)
|
22
|
-
when
|
22
|
+
when Entry::Article
|
23
23
|
article_path_by_article(args.first)
|
24
24
|
else
|
25
25
|
raise TypeError, "wrong argument type #{args.first.class} (expected Date, Time or Retter::Entry::Article)"
|
data/lib/retter/page.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
+
require 'tilt'
|
3
4
|
require 'haml'
|
4
5
|
require 'nokogiri'
|
5
6
|
|
@@ -7,7 +8,7 @@ module Retter
|
|
7
8
|
module Page
|
8
9
|
require 'retter/page/view_helper'
|
9
10
|
|
10
|
-
include
|
11
|
+
include Stationery
|
11
12
|
|
12
13
|
attr_reader :path_prefix, :title
|
13
14
|
|
@@ -17,10 +18,10 @@ module Retter
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def print
|
20
|
-
part =
|
21
|
-
part_layout_pathname.
|
21
|
+
part = Tilt.new(
|
22
|
+
part_layout_pathname.to_path,
|
22
23
|
ugly: true,
|
23
|
-
filename: part_layout_pathname.
|
24
|
+
filename: part_layout_pathname.to_path
|
24
25
|
).render(view_scope)
|
25
26
|
|
26
27
|
print_with_layout part
|
@@ -31,7 +32,7 @@ module Retter
|
|
31
32
|
end
|
32
33
|
|
33
34
|
def path
|
34
|
-
pathname.
|
35
|
+
pathname.to_path
|
35
36
|
end
|
36
37
|
|
37
38
|
def part_layout_pathname
|
@@ -48,7 +49,8 @@ module Retter
|
|
48
49
|
end
|
49
50
|
|
50
51
|
def layout_renderer
|
51
|
-
|
52
|
+
layout_file = Pages.layout_file.to_path
|
53
|
+
@layout_renderer ||= Tilt.new(layout_file, ugly: true, filename: layout_file)
|
52
54
|
end
|
53
55
|
|
54
56
|
def fix_path(html, prefix='./')
|
data/lib/retter/pages/article.rb
CHANGED
@@ -1,39 +1,41 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
3
|
+
module Retter
|
4
|
+
class Pages::Article
|
5
|
+
include Page
|
6
|
+
|
7
|
+
attr_reader :article
|
8
|
+
|
9
|
+
def initialize(article)
|
10
|
+
super()
|
11
|
+
@path_prefix = '../../'
|
12
|
+
@article = article
|
13
|
+
@title = "#{article.title} - #{config.title}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def pathname
|
17
|
+
Pages.entry_dir(article.entry.date).join("#{article.id}.html")
|
18
|
+
end
|
19
|
+
|
20
|
+
def part_layout_pathname
|
21
|
+
Pages.find_layout_path('article')
|
22
|
+
end
|
23
|
+
|
24
|
+
def print
|
25
|
+
options = {entry: article.entry, article: article}
|
26
|
+
part = Tilt.new(
|
27
|
+
part_layout_pathname.to_path,
|
28
|
+
ugly: true,
|
29
|
+
filename: part_layout_pathname.to_path
|
30
|
+
).render(view_scope, options)
|
31
|
+
|
32
|
+
mkdir
|
33
|
+
print_with_layout part
|
34
|
+
end
|
35
|
+
|
36
|
+
def mkdir
|
37
|
+
entry_dir = Pages.entry_dir(article.entry.date)
|
38
|
+
entry_dir.mkdir unless entry_dir.directory?
|
39
|
+
end
|
38
40
|
end
|
39
41
|
end
|
data/lib/retter/pages/entry.rb
CHANGED
@@ -1,32 +1,35 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
module Retter
|
4
|
+
class Pages::Entry
|
5
|
+
include Page
|
5
6
|
|
6
|
-
|
7
|
+
attr_reader :entry
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
@path_prefix = '../'
|
11
|
-
@entry = entry
|
12
|
-
@title = "#{entry.date} - #{config.title}"
|
13
|
-
end
|
9
|
+
def initialize(entry)
|
10
|
+
super()
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
@path_prefix = '../'
|
13
|
+
@entry = entry
|
14
|
+
@title = "#{entry.date} - #{config.title}"
|
15
|
+
end
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
def pathname
|
18
|
+
Pages.entry_file(entry.date)
|
19
|
+
end
|
20
|
+
|
21
|
+
def part_layout_pathname
|
22
|
+
Pages.find_layout_path('entry')
|
23
|
+
end
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
def print
|
26
|
+
part = Tilt.new(
|
27
|
+
part_layout_pathname.to_path,
|
28
|
+
ugly: true,
|
29
|
+
filename: part_layout_pathname.to_path
|
30
|
+
).render(view_scope, entry: entry)
|
29
31
|
|
30
|
-
|
32
|
+
print_with_layout part
|
33
|
+
end
|
31
34
|
end
|
32
35
|
end
|
data/lib/retter/pages/feed.rb
CHANGED
@@ -3,45 +3,47 @@
|
|
3
3
|
require 'builder'
|
4
4
|
require 'uri'
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
module Retter
|
7
|
+
class Pages::Feed
|
8
|
+
include Page
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
def pathname
|
11
|
+
config.retter_home.join('entries.rss')
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
def print
|
15
|
+
pathname.open('w') {|f| f.puts rss }
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
+
private
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
def entry_url(date, id = nil)
|
21
|
+
(URI.parse(config.url) + date.strftime('/entries/%Y%m%d.html')).to_s
|
22
|
+
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
24
|
+
def rss
|
25
|
+
xml = Builder::XmlMarkup.new
|
26
|
+
xml.instruct!
|
27
|
+
xml.rdf:RDF, :xmlns => 'http://purl.org/rss/1.0/',
|
28
|
+
:'xmlns:rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
|
29
|
+
:'xmlns:dc' => 'http://purl.org/dc/elements/1.1/',
|
30
|
+
:'xml:lang' => 'ja' do
|
31
|
+
xml.channel :'rdf:about' => config.url do
|
32
|
+
xml.title config.title
|
33
|
+
xml.link config.url
|
34
|
+
xml.dc:date, entries.empty? ? nil : entries.first.date
|
35
|
+
xml.description config.description
|
36
|
+
xml.items { xml.rdf(:Seq) { entries.each {|e| xml.rdf:li, :'rdf:resource' => entry_url(e.date) } } }
|
37
|
+
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
entries[0...20].each do |entry| # XXX hardcoding
|
40
|
+
xml.item about: entry_url(entry.date) do
|
41
|
+
xml.title entry.date.strftime('%Y/%m/%d')
|
42
|
+
xml.description { xml.cdata! entry.body }
|
43
|
+
xml.dc:date, entry.date
|
44
|
+
xml.link entry_url(entry.date)
|
45
|
+
xml.author config.author
|
46
|
+
end
|
45
47
|
end
|
46
48
|
end
|
47
49
|
end
|
data/lib/retter/pages/index.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
module Retter
|
4
|
+
class Pages::Index
|
5
|
+
include Page
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
def pathname
|
8
|
+
config.retter_home.join('index.html')
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
def part_layout_pathname
|
12
|
+
Pages.find_layout_path('index')
|
13
|
+
end
|
12
14
|
end
|
13
15
|
end
|