2017-01-16 15:27:05 -05:00
|
|
|
|
require 'spec_helper'
|
|
|
|
|
|
|
|
|
|
describe 'Copy as GFM', feature: true, js: true do
|
|
|
|
|
include GitlabMarkdownHelper
|
|
|
|
|
include ActionView::Helpers::JavaScriptHelper
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
@feat = MarkdownFeature.new
|
|
|
|
|
|
|
|
|
|
# `markdown` helper expects a `@project` variable
|
|
|
|
|
@project = @feat.project
|
|
|
|
|
|
|
|
|
|
visit namespace_project_issue_path(@project.namespace, @project, @feat.issue)
|
|
|
|
|
end
|
|
|
|
|
|
2017-01-16 18:11:15 -05:00
|
|
|
|
# The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert GitLab Flavored Markdown (GFM) to HTML.
|
|
|
|
|
# The handlers defined in app/assets/javascripts/copy_as_gfm.js.es6 consequently convert that same HTML to GFM.
|
|
|
|
|
# To make sure these filters and handlers are properly aligned, this spec tests the GFM-to-HTML-to-GFM cycle
|
2017-01-17 13:07:49 -05:00
|
|
|
|
# by verifying (`html_to_gfm(gfm_to_html(gfm)) == gfm`) for a number of examples of GFM for every filter, using the `verify` helper.
|
2017-01-16 15:27:05 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
# These are all in a single `it` for performance reasons.
|
|
|
|
|
it 'works', :aggregate_failures do
|
|
|
|
|
verify(
|
|
|
|
|
'nesting',
|
2017-01-16 18:11:15 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
'> 1. [x] **[$`2 + 2`$ {-=-}{+=+} 2^2 ~~:thumbsup:~~](http://google.com)**'
|
|
|
|
|
)
|
2017-01-16 18:11:15 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
verify(
|
|
|
|
|
'a real world example from the gitlab-ce README',
|
2017-01-16 15:27:05 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
<<-GFM.strip_heredoc
|
|
|
|
|
# GitLab
|
2017-01-16 15:27:05 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
[![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
|
|
|
|
|
[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby)
|
|
|
|
|
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
|
|
|
|
|
[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
|
2017-01-16 15:27:05 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
## Canonical source
|
2017-01-16 15:27:05 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
|
2017-01-16 15:27:05 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
## Open source software to collaborate on code
|
2017-01-16 15:27:05 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
|
2017-01-16 15:27:05 -05:00
|
|
|
|
|
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
- Manage Git repositories with fine grained access controls that keep your code secure
|
2017-01-16 15:27:05 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
- Perform code reviews and enhance collaboration with merge requests
|
2017-01-16 15:27:05 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
- Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications
|
2017-01-16 15:27:05 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
- Each project can also have an issue tracker, issue board, and a wiki
|
|
|
|
|
|
|
|
|
|
- Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises
|
|
|
|
|
|
|
|
|
|
- Completely free and open source (MIT Expat license)
|
|
|
|
|
GFM
|
|
|
|
|
)
|
2017-01-16 18:11:15 -05:00
|
|
|
|
|
|
|
|
|
verify(
|
2017-01-24 17:55:12 -05:00
|
|
|
|
'InlineDiffFilter',
|
|
|
|
|
|
2017-01-16 15:27:05 -05:00
|
|
|
|
'{-Deleted text-}',
|
|
|
|
|
'{+Added text+}'
|
2017-01-16 18:11:15 -05:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
verify(
|
2017-01-24 17:55:12 -05:00
|
|
|
|
'TaskListFilter',
|
|
|
|
|
|
2017-01-16 15:27:05 -05:00
|
|
|
|
'- [ ] Unchecked task',
|
|
|
|
|
'- [x] Checked task',
|
|
|
|
|
'1. [ ] Unchecked numbered task',
|
|
|
|
|
'1. [x] Checked numbered task'
|
2017-01-16 18:11:15 -05:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
verify(
|
2017-01-24 17:55:12 -05:00
|
|
|
|
'ReferenceFilter',
|
|
|
|
|
|
2017-01-16 18:11:15 -05:00
|
|
|
|
# issue reference
|
|
|
|
|
@feat.issue.to_reference,
|
|
|
|
|
# full issue reference
|
|
|
|
|
@feat.issue.to_reference(full: true),
|
|
|
|
|
# issue URL
|
|
|
|
|
namespace_project_issue_url(@project.namespace, @project, @feat.issue),
|
|
|
|
|
# issue URL with note anchor
|
|
|
|
|
namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123'),
|
|
|
|
|
# issue link
|
|
|
|
|
"[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue)})",
|
|
|
|
|
# issue link with note anchor
|
|
|
|
|
"[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123')})",
|
|
|
|
|
)
|
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
verify(
|
|
|
|
|
'AutolinkFilter',
|
2017-01-16 18:11:15 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
'https://example.com'
|
|
|
|
|
)
|
2017-01-16 18:11:15 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
verify(
|
|
|
|
|
'TableOfContentsFilter',
|
2017-01-16 18:11:15 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
'[[_TOC_]]'
|
|
|
|
|
)
|
2017-01-16 18:11:15 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
verify(
|
|
|
|
|
'EmojiFilter',
|
2017-01-16 18:11:15 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
':thumbsup:'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
verify(
|
|
|
|
|
'ImageLinkFilter',
|
|
|
|
|
|
|
|
|
|
'![Image](https://example.com/image.png)'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
verify(
|
|
|
|
|
'VideoLinkFilter',
|
|
|
|
|
|
|
|
|
|
'![Video](https://example.com/video.mp4)'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
verify(
|
|
|
|
|
'MathFilter: math as converted from GFM to HTML',
|
|
|
|
|
|
|
|
|
|
'$`c = \pm\sqrt{a^2 + b^2}`$',
|
2017-01-17 13:07:49 -05:00
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
# math block
|
|
|
|
|
<<-GFM.strip_heredoc
|
|
|
|
|
```math
|
|
|
|
|
c = \pm\sqrt{a^2 + b^2}
|
|
|
|
|
```
|
|
|
|
|
GFM
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
aggregate_failures('MathFilter: math as transformed from HTML to KaTeX') do
|
2017-01-17 13:07:49 -05:00
|
|
|
|
gfm = '$`c = \pm\sqrt{a^2 + b^2}`$'
|
|
|
|
|
|
|
|
|
|
html = <<-HTML.strip_heredoc
|
|
|
|
|
<span class="katex">
|
|
|
|
|
<span class="katex-mathml">
|
|
|
|
|
<math>
|
|
|
|
|
<semantics>
|
|
|
|
|
<mrow>
|
|
|
|
|
<mi>c</mi>
|
|
|
|
|
<mo>=</mo>
|
|
|
|
|
<mo>±</mo>
|
|
|
|
|
<msqrt>
|
|
|
|
|
<mrow>
|
|
|
|
|
<msup>
|
|
|
|
|
<mi>a</mi>
|
|
|
|
|
<mn>2</mn>
|
|
|
|
|
</msup>
|
|
|
|
|
<mo>+</mo>
|
|
|
|
|
<msup>
|
|
|
|
|
<mi>b</mi>
|
|
|
|
|
<mn>2</mn>
|
|
|
|
|
</msup>
|
|
|
|
|
</mrow>
|
|
|
|
|
</msqrt>
|
|
|
|
|
</mrow>
|
|
|
|
|
<annotation encoding="application/x-tex">c = \\pm\\sqrt{a^2 + b^2}</annotation>
|
|
|
|
|
</semantics>
|
|
|
|
|
</math>
|
|
|
|
|
</span>
|
|
|
|
|
<span class="katex-html" aria-hidden="true">
|
|
|
|
|
<span class="strut" style="height: 0.913389em;"></span>
|
|
|
|
|
<span class="strut bottom" style="height: 1.04em; vertical-align: -0.126611em;"></span>
|
|
|
|
|
<span class="base textstyle uncramped">
|
|
|
|
|
<span class="mord mathit">c</span>
|
|
|
|
|
<span class="mrel">=</span>
|
|
|
|
|
<span class="mord">±</span>
|
|
|
|
|
<span class="sqrt mord"><span class="sqrt-sign" style="top: -0.073389em;">
|
|
|
|
|
<span class="style-wrap reset-textstyle textstyle uncramped">√</span>
|
|
|
|
|
</span>
|
|
|
|
|
<span class="vlist">
|
|
|
|
|
<span class="" style="top: 0em;">
|
|
|
|
|
<span class="fontsize-ensurer reset-size5 size5">
|
|
|
|
|
<span class="" style="font-size: 1em;"></span>
|
|
|
|
|
</span>
|
|
|
|
|
<span class="mord textstyle cramped">
|
|
|
|
|
<span class="mord">
|
|
|
|
|
<span class="mord mathit">a</span>
|
|
|
|
|
<span class="msupsub">
|
|
|
|
|
<span class="vlist">
|
|
|
|
|
<span class="" style="top: -0.289em; margin-right: 0.05em;">
|
|
|
|
|
<span class="fontsize-ensurer reset-size5 size5">
|
|
|
|
|
<span class="" style="font-size: 0em;"></span>
|
|
|
|
|
</span>
|
|
|
|
|
<span class="reset-textstyle scriptstyle cramped">
|
|
|
|
|
<span class="mord mathrm">2</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
<span class="baseline-fix">
|
|
|
|
|
<span class="fontsize-ensurer reset-size5 size5">
|
|
|
|
|
<span class="" style="font-size: 0em;"></span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
<span class="mbin">+</span>
|
|
|
|
|
<span class="mord">
|
|
|
|
|
<span class="mord mathit">b</span>
|
|
|
|
|
<span class="msupsub">
|
|
|
|
|
<span class="vlist">
|
|
|
|
|
<span class="" style="top: -0.289em; margin-right: 0.05em;">
|
|
|
|
|
<span class="fontsize-ensurer reset-size5 size5">
|
|
|
|
|
<span class="" style="font-size: 0em;"></span>
|
|
|
|
|
</span>
|
|
|
|
|
<span class="reset-textstyle scriptstyle cramped">
|
|
|
|
|
<span class="mord mathrm">2</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
<span class="baseline-fix">
|
|
|
|
|
<span class="fontsize-ensurer reset-size5 size5">
|
|
|
|
|
<span class="" style="font-size: 0em;"></span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
<span class="" style="top: -0.833389em;">
|
|
|
|
|
<span class="fontsize-ensurer reset-size5 size5">
|
|
|
|
|
<span class="" style="font-size: 1em;"></span>
|
|
|
|
|
</span>
|
|
|
|
|
<span class="reset-textstyle textstyle uncramped sqrt-line"></span>
|
|
|
|
|
</span>
|
|
|
|
|
<span class="baseline-fix">
|
|
|
|
|
<span class="fontsize-ensurer reset-size5 size5">
|
|
|
|
|
<span class="" style="font-size: 1em;"></span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
HTML
|
|
|
|
|
|
|
|
|
|
output_gfm = html_to_gfm(html)
|
|
|
|
|
expect(output_gfm.strip).to eq(gfm.strip)
|
|
|
|
|
end
|
|
|
|
|
|
2017-01-19 11:01:27 -05:00
|
|
|
|
verify(
|
2017-01-24 17:55:12 -05:00
|
|
|
|
'SanitizationFilter',
|
|
|
|
|
|
2017-01-19 11:01:27 -05:00
|
|
|
|
<<-GFM.strip_heredoc
|
2017-02-08 20:46:01 -05:00
|
|
|
|
<a name="named-anchor"></a>
|
|
|
|
|
|
2017-01-19 11:01:27 -05:00
|
|
|
|
<sub>sub</sub>
|
|
|
|
|
|
|
|
|
|
<dl>
|
|
|
|
|
<dt>dt</dt>
|
|
|
|
|
<dd>dd</dd>
|
|
|
|
|
</dl>
|
|
|
|
|
|
|
|
|
|
<kbd>kbd</kbd>
|
|
|
|
|
|
|
|
|
|
<q>q</q>
|
|
|
|
|
|
|
|
|
|
<samp>samp</samp>
|
|
|
|
|
|
|
|
|
|
<var>var</var>
|
|
|
|
|
|
|
|
|
|
<ruby>ruby</ruby>
|
|
|
|
|
|
|
|
|
|
<rt>rt</rt>
|
|
|
|
|
|
|
|
|
|
<rp>rp</rp>
|
|
|
|
|
|
|
|
|
|
<abbr>abbr</abbr>
|
|
|
|
|
GFM
|
|
|
|
|
)
|
|
|
|
|
|
2017-01-16 18:11:15 -05:00
|
|
|
|
verify(
|
2017-01-24 17:55:12 -05:00
|
|
|
|
'SanitizationFilter',
|
|
|
|
|
|
2017-01-17 13:07:49 -05:00
|
|
|
|
<<-GFM.strip_heredoc,
|
|
|
|
|
```
|
|
|
|
|
Plain text
|
|
|
|
|
```
|
|
|
|
|
GFM
|
|
|
|
|
|
2017-01-19 14:54:59 -05:00
|
|
|
|
<<-GFM.strip_heredoc,
|
2017-01-17 13:07:49 -05:00
|
|
|
|
```ruby
|
|
|
|
|
def foo
|
|
|
|
|
bar
|
|
|
|
|
end
|
2017-01-16 18:11:15 -05:00
|
|
|
|
```
|
|
|
|
|
GFM
|
2017-01-19 14:54:59 -05:00
|
|
|
|
|
|
|
|
|
<<-GFM.strip_heredoc
|
|
|
|
|
Foo
|
2017-01-24 17:55:12 -05:00
|
|
|
|
|
2017-01-19 14:54:59 -05:00
|
|
|
|
This is an example of GFM
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
Code goes here
|
|
|
|
|
```
|
|
|
|
|
GFM
|
2017-01-16 18:11:15 -05:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
verify(
|
2017-01-24 17:55:12 -05:00
|
|
|
|
'MarkdownFilter',
|
|
|
|
|
|
2017-01-19 14:47:54 -05:00
|
|
|
|
"Line with two spaces at the end \nto insert a linebreak",
|
|
|
|
|
|
2017-01-16 15:27:05 -05:00
|
|
|
|
'`code`',
|
|
|
|
|
'`` code with ` ticks ``',
|
|
|
|
|
|
|
|
|
|
'> Quote',
|
2017-01-17 13:07:49 -05:00
|
|
|
|
|
2017-01-16 18:11:15 -05:00
|
|
|
|
# multiline quote
|
|
|
|
|
<<-GFM.strip_heredoc,
|
|
|
|
|
> Multiline
|
|
|
|
|
> Quote
|
|
|
|
|
>
|
|
|
|
|
> With multiple paragraphs
|
|
|
|
|
GFM
|
2017-01-16 15:27:05 -05:00
|
|
|
|
|
|
|
|
|
'![Image](https://example.com/image.png)',
|
|
|
|
|
|
|
|
|
|
'# Heading with no anchor link',
|
|
|
|
|
|
|
|
|
|
'[Link](https://example.com)',
|
|
|
|
|
|
|
|
|
|
'- List item',
|
2017-01-16 18:11:15 -05:00
|
|
|
|
|
|
|
|
|
# multiline list item
|
|
|
|
|
<<-GFM.strip_heredoc,
|
|
|
|
|
- Multiline
|
2017-01-19 14:47:54 -05:00
|
|
|
|
List item
|
2017-01-16 18:11:15 -05:00
|
|
|
|
GFM
|
|
|
|
|
|
|
|
|
|
# nested lists
|
|
|
|
|
<<-GFM.strip_heredoc,
|
|
|
|
|
- Nested
|
|
|
|
|
|
|
|
|
|
|
2017-01-19 14:47:54 -05:00
|
|
|
|
- Lists
|
|
|
|
|
GFM
|
|
|
|
|
|
|
|
|
|
# list with blockquote
|
|
|
|
|
<<-GFM.strip_heredoc,
|
|
|
|
|
- List
|
|
|
|
|
|
|
|
|
|
> Blockquote
|
2017-01-16 18:11:15 -05:00
|
|
|
|
GFM
|
|
|
|
|
|
2017-01-16 15:27:05 -05:00
|
|
|
|
'1. Numbered list item',
|
2017-01-16 18:11:15 -05:00
|
|
|
|
|
|
|
|
|
# multiline numbered list item
|
|
|
|
|
<<-GFM.strip_heredoc,
|
|
|
|
|
1. Multiline
|
2017-01-19 14:47:54 -05:00
|
|
|
|
Numbered list item
|
2017-01-16 18:11:15 -05:00
|
|
|
|
GFM
|
|
|
|
|
|
|
|
|
|
# nested numbered list
|
|
|
|
|
<<-GFM.strip_heredoc,
|
|
|
|
|
1. Nested
|
|
|
|
|
|
|
|
|
|
|
2017-01-19 14:47:54 -05:00
|
|
|
|
1. Numbered lists
|
2017-01-16 18:11:15 -05:00
|
|
|
|
GFM
|
2017-01-16 15:27:05 -05:00
|
|
|
|
|
|
|
|
|
'# Heading',
|
|
|
|
|
'## Heading',
|
|
|
|
|
'### Heading',
|
|
|
|
|
'#### Heading',
|
|
|
|
|
'##### Heading',
|
|
|
|
|
'###### Heading',
|
|
|
|
|
|
|
|
|
|
'**Bold**',
|
|
|
|
|
|
|
|
|
|
'_Italics_',
|
|
|
|
|
|
|
|
|
|
'~~Strikethrough~~',
|
|
|
|
|
|
|
|
|
|
'2^2',
|
|
|
|
|
|
|
|
|
|
'-----',
|
|
|
|
|
|
2017-01-16 18:11:15 -05:00
|
|
|
|
# table
|
|
|
|
|
<<-GFM.strip_heredoc,
|
|
|
|
|
| Centered | Right | Left |
|
2017-01-19 14:47:54 -05:00
|
|
|
|
|:--------:|------:|------|
|
2017-01-16 18:11:15 -05:00
|
|
|
|
| Foo | Bar | **Baz** |
|
|
|
|
|
| Foo | Bar | **Baz** |
|
|
|
|
|
GFM
|
2017-01-19 14:47:54 -05:00
|
|
|
|
|
|
|
|
|
# table with empty heading
|
|
|
|
|
<<-GFM.strip_heredoc,
|
|
|
|
|
| | x | y |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| a | 1 | 0 |
|
|
|
|
|
| b | 0 | 1 |
|
|
|
|
|
GFM
|
2017-01-16 18:11:15 -05:00
|
|
|
|
)
|
2017-01-16 15:27:05 -05:00
|
|
|
|
end
|
|
|
|
|
|
2017-01-16 18:11:15 -05:00
|
|
|
|
alias_method :gfm_to_html, :markdown
|
|
|
|
|
|
2017-01-16 15:27:05 -05:00
|
|
|
|
def html_to_gfm(html)
|
|
|
|
|
js = <<-JS.strip_heredoc
|
|
|
|
|
(function(html) {
|
|
|
|
|
var node = document.createElement('div');
|
|
|
|
|
node.innerHTML = html;
|
|
|
|
|
return window.gl.CopyAsGFM.nodeToGFM(node);
|
|
|
|
|
})("#{escape_javascript(html)}")
|
|
|
|
|
JS
|
|
|
|
|
page.evaluate_script(js)
|
|
|
|
|
end
|
|
|
|
|
|
2017-01-24 17:55:12 -05:00
|
|
|
|
def verify(label, *gfms)
|
|
|
|
|
aggregate_failures(label) do
|
2017-01-16 18:11:15 -05:00
|
|
|
|
gfms.each do |gfm|
|
|
|
|
|
html = gfm_to_html(gfm)
|
|
|
|
|
output_gfm = html_to_gfm(html)
|
|
|
|
|
expect(output_gfm.strip).to eq(gfm.strip)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2017-01-16 15:27:05 -05:00
|
|
|
|
# Fake a `current_user` helper
|
|
|
|
|
def current_user
|
|
|
|
|
@feat.user
|
|
|
|
|
end
|
|
|
|
|
end
|