leg 0.0.1 → 0.0.2
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 +5 -5
- data/.gitignore +10 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +34 -0
- data/LICENSE +21 -0
- data/README.md +59 -0
- data/Rakefile +11 -0
- data/TUTORIAL.md +243 -0
- data/bin/console +9 -0
- data/bin/setup +6 -0
- data/exe/leg +6 -0
- data/leg.gemspec +31 -0
- data/lib/leg.rb +27 -0
- data/lib/leg/cli.rb +42 -0
- data/lib/leg/commands.rb +19 -0
- data/lib/leg/commands/amend.rb +49 -0
- data/lib/leg/commands/base_command.rb +99 -0
- data/lib/leg/commands/build.rb +126 -0
- data/lib/leg/commands/commit.rb +48 -0
- data/lib/leg/commands/diff.rb +29 -0
- data/lib/leg/commands/help.rb +43 -0
- data/lib/leg/commands/init.rb +46 -0
- data/lib/leg/commands/reset.rb +26 -0
- data/lib/leg/commands/resolve.rb +31 -0
- data/lib/leg/commands/save.rb +31 -0
- data/lib/leg/commands/status.rb +54 -0
- data/lib/leg/commands/step.rb +31 -0
- data/lib/leg/config.rb +42 -0
- data/lib/leg/default_templates.rb +340 -0
- data/lib/leg/diff.rb +151 -0
- data/lib/leg/diff_transformers.rb +11 -0
- data/lib/leg/diff_transformers/base_transformer.rb +13 -0
- data/lib/leg/diff_transformers/fold_sections.rb +89 -0
- data/lib/leg/diff_transformers/omit_adjacent_removals.rb +38 -0
- data/lib/leg/diff_transformers/syntax_highlight.rb +32 -0
- data/lib/leg/diff_transformers/trim_blank_lines.rb +25 -0
- data/lib/leg/line.rb +83 -0
- data/lib/leg/markdown.rb +20 -0
- data/lib/leg/page.rb +27 -0
- data/lib/leg/representations.rb +9 -0
- data/lib/leg/representations/base_representation.rb +42 -0
- data/lib/leg/representations/git.rb +388 -0
- data/lib/leg/representations/litdiff.rb +85 -0
- data/lib/leg/step.rb +16 -0
- data/lib/leg/template.rb +95 -0
- data/lib/leg/tutorial.rb +49 -0
- data/lib/leg/version.rb +3 -0
- metadata +112 -38
- data/bin/leg +0 -9
- data/lib/snaptoken.rb +0 -24
- data/lib/snaptoken/cli.rb +0 -61
- data/lib/snaptoken/commands.rb +0 -13
- data/lib/snaptoken/commands/amend.rb +0 -27
- data/lib/snaptoken/commands/base_command.rb +0 -92
- data/lib/snaptoken/commands/build.rb +0 -107
- data/lib/snaptoken/commands/commit.rb +0 -27
- data/lib/snaptoken/commands/help.rb +0 -38
- data/lib/snaptoken/commands/resolve.rb +0 -27
- data/lib/snaptoken/commands/status.rb +0 -21
- data/lib/snaptoken/commands/step.rb +0 -35
- data/lib/snaptoken/default_templates.rb +0 -287
- data/lib/snaptoken/diff.rb +0 -180
- data/lib/snaptoken/diff_line.rb +0 -54
- data/lib/snaptoken/diff_transformers.rb +0 -9
- data/lib/snaptoken/diff_transformers/base_transformer.rb +0 -9
- data/lib/snaptoken/diff_transformers/fold_sections.rb +0 -85
- data/lib/snaptoken/diff_transformers/omit_adjacent_removals.rb +0 -28
- data/lib/snaptoken/diff_transformers/trim_blank_lines.rb +0 -21
- data/lib/snaptoken/markdown.rb +0 -18
- data/lib/snaptoken/page.rb +0 -64
- data/lib/snaptoken/representations.rb +0 -8
- data/lib/snaptoken/representations/base_representation.rb +0 -38
- data/lib/snaptoken/representations/git.rb +0 -262
- data/lib/snaptoken/representations/litdiff.rb +0 -81
- data/lib/snaptoken/step.rb +0 -27
- data/lib/snaptoken/template.rb +0 -53
- data/lib/snaptoken/tutorial.rb +0 -64
data/lib/leg/markdown.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Leg
|
2
|
+
module Markdown
|
3
|
+
class HTMLRouge < Redcarpet::Render::HTML
|
4
|
+
include Rouge::Plugins::Redcarpet
|
5
|
+
end
|
6
|
+
|
7
|
+
HTML_RENDERER = HTMLRouge.new(with_toc_data: true)
|
8
|
+
MARKDOWN_RENDERER = Redcarpet::Markdown.new(HTML_RENDERER, fenced_code_blocks: true)
|
9
|
+
|
10
|
+
def self.render(source)
|
11
|
+
html = MARKDOWN_RENDERER.render(source)
|
12
|
+
html = Redcarpet::Render::SmartyPants.render(html)
|
13
|
+
html.gsub!(/<\/code>‘/) { "</code>’" }
|
14
|
+
html.gsub!(/^\s*<h([23456]) id="([^"]+)">(.+)<\/h\d>$/) {
|
15
|
+
"<h#{$1} id=\"#{$2}\"><a href=\"##{$2}\">#{$3}</a></h#{$1}>"
|
16
|
+
}
|
17
|
+
html
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/leg/page.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Leg
|
2
|
+
class Page
|
3
|
+
attr_accessor :filename, :steps, :footer_text
|
4
|
+
|
5
|
+
def initialize(filename = "tutorial")
|
6
|
+
@filename = filename
|
7
|
+
@steps = []
|
8
|
+
@footer_text = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def <<(step)
|
12
|
+
@steps << step
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def empty?
|
17
|
+
@steps.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
def title
|
21
|
+
first_line = @steps.first ? @steps.first.text.lines.first : (@footer_text ? @footer_text.lines.first : nil)
|
22
|
+
if first_line && first_line.start_with?("# ")
|
23
|
+
first_line[2..-1].strip
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Leg
|
2
|
+
module Representations
|
3
|
+
class BaseRepresentation
|
4
|
+
def initialize(config)
|
5
|
+
@config = config
|
6
|
+
end
|
7
|
+
|
8
|
+
# Should save tutorial to disk.
|
9
|
+
def save!(tutorial, options = {})
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
# Should load tutorial from disk, and return it.
|
14
|
+
def load!(options = {})
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns true if this representation has been modified by the user since the
|
19
|
+
# last sync.
|
20
|
+
def modified?
|
21
|
+
synced_at = @config.last_synced_at
|
22
|
+
repr_modified_at = modified_at
|
23
|
+
return false if synced_at.nil? or repr_modified_at.nil?
|
24
|
+
|
25
|
+
repr_modified_at > synced_at
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns true if this representation currently exists on disk.
|
29
|
+
def exists?
|
30
|
+
!modified_at.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Should return the Time the representation on disk was last modified, or nil
|
36
|
+
# if the representation doesn't exist.
|
37
|
+
def modified_at
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,388 @@
|
|
1
|
+
module Leg
|
2
|
+
module Representations
|
3
|
+
class Git < BaseRepresentation
|
4
|
+
def save!(tutorial, options = {})
|
5
|
+
FileUtils.rm_rf(repo_path)
|
6
|
+
FileUtils.mkdir_p(repo_path)
|
7
|
+
|
8
|
+
FileUtils.cd(repo_path) do
|
9
|
+
repo = Rugged::Repository.init_at(".")
|
10
|
+
|
11
|
+
step_num = 1
|
12
|
+
tutorial.pages.each do |page|
|
13
|
+
message = "~~~ #{page.filename}"
|
14
|
+
message << "\n\n#{page.footer_text}" if page.footer_text
|
15
|
+
add_commit(repo, nil, message, step_num)
|
16
|
+
page.steps.each do |step|
|
17
|
+
message = "#{step.summary}\n\n#{step.text}".strip
|
18
|
+
add_commit(repo, step.to_patch, message, step_num)
|
19
|
+
|
20
|
+
yield step_num if block_given?
|
21
|
+
step_num += 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
#if options[:extra_path]
|
26
|
+
# FileUtils.cp_r(File.join(options[:extra_path], "."), ".")
|
27
|
+
# add_commit(repo, nil, "-", step_num, counter)
|
28
|
+
#end
|
29
|
+
|
30
|
+
repo.checkout_head(strategy: :force) if repo.branches["master"]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Options:
|
35
|
+
# full_diffs: If true, diffs contain the entire file in one hunk instead of
|
36
|
+
# multiple contextual hunks.
|
37
|
+
# diffs_ignore_whitespace: If true, diffs don't show changes to lines when
|
38
|
+
# only the amount of whitespace is changed.
|
39
|
+
def load!(options = {})
|
40
|
+
git_diff_options = {}
|
41
|
+
git_diff_options[:context_lines] = 100_000 if options[:full_diffs]
|
42
|
+
git_diff_options[:ignore_whitespace_change] = true if options[:diffs_ignore_whitespace]
|
43
|
+
|
44
|
+
page = nil
|
45
|
+
tutorial = Leg::Tutorial.new(@config)
|
46
|
+
each_step(git_diff_options) do |step_num, commit, summary, text, patches|
|
47
|
+
if patches.empty?
|
48
|
+
if summary =~ /^~~~ (.+)$/
|
49
|
+
tutorial << page unless page.nil?
|
50
|
+
|
51
|
+
page = Leg::Page.new($1)
|
52
|
+
page.footer_text = text unless text.empty?
|
53
|
+
else
|
54
|
+
puts "Warning: ignoring empty commit."
|
55
|
+
end
|
56
|
+
else
|
57
|
+
patch = patches.map(&:to_s).join("\n")
|
58
|
+
step_diffs = Leg::Diff.parse(patch)
|
59
|
+
|
60
|
+
page ||= Leg::Page.new
|
61
|
+
page << Leg::Step.new(step_num, summary, text, step_diffs)
|
62
|
+
|
63
|
+
yield step_num if block_given?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
tutorial << page unless page.nil?
|
67
|
+
tutorial
|
68
|
+
end
|
69
|
+
|
70
|
+
def copy_repo_to_step!
|
71
|
+
FileUtils.mkdir_p(step_path)
|
72
|
+
FileUtils.rm_rf(File.join(step_path, "."), secure: true)
|
73
|
+
FileUtils.cd(repo_path) do
|
74
|
+
files = Dir.glob("*", File::FNM_DOTMATCH) - [".", "..", ".git"]
|
75
|
+
files.each do |f|
|
76
|
+
FileUtils.cp_r(f, File.join(step_path, f))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def copy_step_to_repo!
|
82
|
+
FileUtils.mv(
|
83
|
+
File.join(repo_path, ".git"),
|
84
|
+
File.join(repo_path, "../.gittemp")
|
85
|
+
)
|
86
|
+
FileUtils.rm_rf(File.join(repo_path, "."), secure: true)
|
87
|
+
FileUtils.mv(
|
88
|
+
File.join(repo_path, "../.gittemp"),
|
89
|
+
File.join(repo_path, ".git")
|
90
|
+
)
|
91
|
+
FileUtils.cd(step_path) do
|
92
|
+
files = Dir.glob("*", File::FNM_DOTMATCH) - [".", ".."]
|
93
|
+
files.each do |f|
|
94
|
+
FileUtils.cp_r(f, File.join(repo_path, f))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def repo_path
|
100
|
+
File.join(@config.path, ".leg/repo")
|
101
|
+
end
|
102
|
+
|
103
|
+
def repo
|
104
|
+
@repo ||= Rugged::Repository.new(repo_path)
|
105
|
+
end
|
106
|
+
|
107
|
+
def each_commit(options = {})
|
108
|
+
walker = Rugged::Walker.new(repo)
|
109
|
+
walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
|
110
|
+
|
111
|
+
master_commit = repo.branches["master"].target
|
112
|
+
walker.push(master_commit)
|
113
|
+
|
114
|
+
return [] if master_commit.oid == options[:after]
|
115
|
+
walker.hide(options[:after]) if options[:after]
|
116
|
+
|
117
|
+
return walker.to_a if not block_given?
|
118
|
+
|
119
|
+
walker.each do |commit|
|
120
|
+
yield commit
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
alias_method :commits, :each_commit
|
125
|
+
|
126
|
+
def each_step(git_diff_options = {})
|
127
|
+
empty_tree = Rugged::Tree.empty(repo)
|
128
|
+
step_num = 1
|
129
|
+
each_commit do |commit|
|
130
|
+
commit_message = commit.message.strip
|
131
|
+
summary = commit_message.lines.first.strip
|
132
|
+
text = (commit_message.lines[2..-1] || []).join.strip
|
133
|
+
next if commit_message == "-"
|
134
|
+
commit_message = "" if commit_message == "~"
|
135
|
+
last_commit = commit.parents.first
|
136
|
+
diff = (last_commit || empty_tree).diff(commit, git_diff_options)
|
137
|
+
patches = diff.each_patch.to_a
|
138
|
+
|
139
|
+
if patches.empty?
|
140
|
+
yield nil, commit, summary, text, patches
|
141
|
+
else
|
142
|
+
yield step_num, commit, summary, text, patches
|
143
|
+
step_num += 1
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def init!
|
149
|
+
FileUtils.mkdir_p(repo_path)
|
150
|
+
FileUtils.cd(repo_path) { `git init` }
|
151
|
+
end
|
152
|
+
|
153
|
+
def checkout!(step_number)
|
154
|
+
each_step do |cur_step, commit|
|
155
|
+
if cur_step == step_number
|
156
|
+
FileUtils.cd(repo_path) { `git checkout #{commit.oid} 2>/dev/null` }
|
157
|
+
save_state(load_state.step!(step_number))
|
158
|
+
copy_repo_to_step!
|
159
|
+
return true
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def commit!(options = {})
|
165
|
+
copy_step_to_repo!
|
166
|
+
remaining_commits = repo.branches["master"] ? commits(after: repo.head.target.oid).map(&:oid) : []
|
167
|
+
FileUtils.cd(repo_path) do
|
168
|
+
`git add -A`
|
169
|
+
|
170
|
+
cmd = ["git", "commit", "-q"]
|
171
|
+
cmd << "--amend" if options[:amend]
|
172
|
+
cmd << "-m" << options[:message] if options[:message]
|
173
|
+
cmd << "--no-edit" if options[:use_default_message] && options[:amend]
|
174
|
+
cmd << "-m" << "Untitled step" if options[:use_default_message] && !options[:amend]
|
175
|
+
system(*cmd)
|
176
|
+
end
|
177
|
+
if options[:amend]
|
178
|
+
save_state(load_state.amend!)
|
179
|
+
else
|
180
|
+
save_state(load_state.add_commit!)
|
181
|
+
end
|
182
|
+
if options[:no_rebase]
|
183
|
+
save_remaining_commits(remaining_commits)
|
184
|
+
true
|
185
|
+
else
|
186
|
+
rebase!(remaining_commits)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def resolve!
|
191
|
+
copy_step_to_repo!
|
192
|
+
FileUtils.cd(repo_path) do
|
193
|
+
`git add -A`
|
194
|
+
`git -c core.editor=true cherry-pick --allow-empty --allow-empty-message --keep-redundant-commits --continue 2>/dev/null`
|
195
|
+
end
|
196
|
+
rebase!(load_remaining_commits)
|
197
|
+
end
|
198
|
+
|
199
|
+
def rebase_remaining!
|
200
|
+
rebase!(load_remaining_commits)
|
201
|
+
end
|
202
|
+
|
203
|
+
def rebase!(remaining_commits)
|
204
|
+
FileUtils.cd(repo_path) do
|
205
|
+
remaining_commits.each.with_index do |commit, commit_idx|
|
206
|
+
`git cherry-pick --allow-empty --allow-empty-message --keep-redundant-commits #{commit} 2>/dev/null`
|
207
|
+
|
208
|
+
if not $?.success?
|
209
|
+
copy_repo_to_step!
|
210
|
+
save_remaining_commits(remaining_commits[(commit_idx+1)..-1])
|
211
|
+
save_state(load_state.conflict!)
|
212
|
+
return false
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
save_remaining_commits(nil)
|
218
|
+
save_state(nil)
|
219
|
+
|
220
|
+
repo.references.update(repo.branches["master"], repo.head.target_id)
|
221
|
+
repo.head = "refs/heads/master"
|
222
|
+
|
223
|
+
copy_repo_to_step!
|
224
|
+
|
225
|
+
true
|
226
|
+
end
|
227
|
+
|
228
|
+
def reset!
|
229
|
+
save_state(nil)
|
230
|
+
save_remaining_commits(nil)
|
231
|
+
FileUtils.cd(repo_path) do
|
232
|
+
`git cherry-pick --abort 2>/dev/null`
|
233
|
+
end
|
234
|
+
repo.head = "refs/heads/master"
|
235
|
+
repo.checkout_head(strategy: :force)
|
236
|
+
copy_repo_to_step!
|
237
|
+
end
|
238
|
+
|
239
|
+
def state
|
240
|
+
load_state
|
241
|
+
end
|
242
|
+
|
243
|
+
private
|
244
|
+
|
245
|
+
def step_path
|
246
|
+
File.join(@config.path, "step")
|
247
|
+
end
|
248
|
+
|
249
|
+
def remaining_commits_path
|
250
|
+
File.join(@config.path, ".leg/remaining_commits")
|
251
|
+
end
|
252
|
+
|
253
|
+
def modified_at
|
254
|
+
if File.exist? repo_path
|
255
|
+
repo = Rugged::Repository.new(repo_path)
|
256
|
+
if master = repo.branches["master"]
|
257
|
+
master.target.time
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def state_path
|
263
|
+
File.join(@config.path, ".leg/state.yml")
|
264
|
+
end
|
265
|
+
|
266
|
+
def load_state
|
267
|
+
@state ||=
|
268
|
+
if File.exist?(state_path)
|
269
|
+
YAML.load_file(state_path)
|
270
|
+
else
|
271
|
+
State.new
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def save_state(state)
|
276
|
+
@state = state
|
277
|
+
if state.nil?
|
278
|
+
FileUtils.rm_f(state_path)
|
279
|
+
else
|
280
|
+
File.write(state_path, state.to_yaml)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def load_remaining_commits
|
285
|
+
if File.exist?(remaining_commits_path)
|
286
|
+
File.readlines(remaining_commits_path).map(&:strip).reject(&:empty?)
|
287
|
+
else
|
288
|
+
[]
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def save_remaining_commits(remaining_commits)
|
293
|
+
if remaining_commits && !remaining_commits.empty?
|
294
|
+
File.write(remaining_commits_path, remaining_commits.join("\n"))
|
295
|
+
else
|
296
|
+
FileUtils.rm_f(remaining_commits_path)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def add_commit(repo, diff, message, step_num)
|
301
|
+
message ||= "~"
|
302
|
+
message.strip!
|
303
|
+
message = "~" if message.empty?
|
304
|
+
|
305
|
+
if diff
|
306
|
+
stdin = IO.popen("git apply -", "w")
|
307
|
+
stdin.write diff
|
308
|
+
stdin.close
|
309
|
+
end
|
310
|
+
|
311
|
+
index = repo.index
|
312
|
+
index.read_tree(repo.head.target.tree) unless repo.empty?
|
313
|
+
|
314
|
+
Dir["**/*"].each do |path|
|
315
|
+
unless File.directory?(path)
|
316
|
+
oid = repo.write(File.read(path), :blob)
|
317
|
+
index.add(path: path, oid: oid, mode: 0100644)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
options = {}
|
322
|
+
options[:tree] = index.write_tree(repo)
|
323
|
+
if @config.options[:repo_author_name]
|
324
|
+
options[:author] = {
|
325
|
+
name: @config.options[:repo_author_name],
|
326
|
+
email: @config.options[:repo_author_email],
|
327
|
+
time: Time.now
|
328
|
+
}
|
329
|
+
options[:committer] = options[:author]
|
330
|
+
end
|
331
|
+
options[:message] = message
|
332
|
+
options[:parents] = repo.empty? ? [] : [repo.head.target]
|
333
|
+
options[:update_ref] = "HEAD"
|
334
|
+
|
335
|
+
commit_oid = Rugged::Commit.create(repo, options)
|
336
|
+
|
337
|
+
if diff
|
338
|
+
repo.references.create("refs/tags/step-#{step_num}", commit_oid)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
class State
|
343
|
+
attr_accessor :step_number, :operation, :args, :conflict
|
344
|
+
|
345
|
+
def initialize
|
346
|
+
@step_number = nil
|
347
|
+
@operation = nil
|
348
|
+
@args = []
|
349
|
+
@conflict = false
|
350
|
+
end
|
351
|
+
|
352
|
+
def step!(step_number)
|
353
|
+
@step_number = step_number
|
354
|
+
self
|
355
|
+
end
|
356
|
+
|
357
|
+
def add_commit!
|
358
|
+
if @operation.nil?
|
359
|
+
@operation = :commit
|
360
|
+
@args = [1, false]
|
361
|
+
elsif @operation == :commit
|
362
|
+
@args[0] += 1
|
363
|
+
else
|
364
|
+
raise "@operation must be :commit or nil"
|
365
|
+
end
|
366
|
+
self
|
367
|
+
end
|
368
|
+
|
369
|
+
def amend!
|
370
|
+
if @operation.nil?
|
371
|
+
@operation = :commit
|
372
|
+
@args = [0, true]
|
373
|
+
elsif @operation == :commit
|
374
|
+
@args[1] = true
|
375
|
+
else
|
376
|
+
raise "@operation must be :commit or nil"
|
377
|
+
end
|
378
|
+
self
|
379
|
+
end
|
380
|
+
|
381
|
+
def conflict!
|
382
|
+
@conflict = true
|
383
|
+
self
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|