startling 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 +7 -0
- data/.gitignore +21 -0
- data/.ruby-gemset +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +78 -0
- data/Rakefile +15 -0
- data/bin/start +5 -0
- data/lib/generators/startling/configuration_generator.rb +78 -0
- data/lib/startling.rb +30 -0
- data/lib/startling/cache.rb +20 -0
- data/lib/startling/cli_options.rb +46 -0
- data/lib/startling/colorize_string.rb +58 -0
- data/lib/startling/command.rb +67 -0
- data/lib/startling/commands/base.rb +83 -0
- data/lib/startling/commands/check_for_local_mods.rb +17 -0
- data/lib/startling/commands/create_branch.rb +53 -0
- data/lib/startling/commands/create_pull_request.rb +35 -0
- data/lib/startling/commands/label_pull_request.rb +15 -0
- data/lib/startling/configuration.rb +106 -0
- data/lib/startling/git_local.rb +61 -0
- data/lib/startling/github.rb +16 -0
- data/lib/startling/github/api.rb +106 -0
- data/lib/startling/github/pull_request.rb +54 -0
- data/lib/startling/github/repo.rb +44 -0
- data/lib/startling/handlers/default_pull_request_handler.rb +21 -0
- data/lib/startling/handlers/pull_request_handler_base.rb +21 -0
- data/lib/startling/markdown.rb +7 -0
- data/lib/startling/shell.rb +11 -0
- data/lib/startling/version.rb +3 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/startling/commands/base_spec.rb +32 -0
- data/spec/startling/configuration_spec.rb +127 -0
- data/spec/startling/git_local_spec.rb +22 -0
- data/spec/startling/github/pull_request_spec.rb +37 -0
- data/spec/startling_configuration_spec.rb +16 -0
- data/spec/startling_spec.rb +122 -0
- data/spec/support/dotenv.rb +2 -0
- data/spec/support/tokens.rb +5 -0
- data/spec/support/vcr.rb +16 -0
- data/spec/vcr_cassettes/bin_start_starts_stories.yml +564 -0
- data/spec/vcr_cassettes/bin_start_starts_stories_pr_body.yml +644 -0
- data/startling.gemspec +36 -0
- metadata +297 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
module Startling
|
2
|
+
module Github
|
3
|
+
class PullRequest
|
4
|
+
attr_reader :attributes
|
5
|
+
attr_accessor :labels
|
6
|
+
|
7
|
+
def initialize(attributes, prefetch_data: true)
|
8
|
+
@attributes = attributes
|
9
|
+
prefetch_data if prefetch_data
|
10
|
+
end
|
11
|
+
|
12
|
+
def id
|
13
|
+
attributes.number
|
14
|
+
end
|
15
|
+
|
16
|
+
def title
|
17
|
+
attributes.title
|
18
|
+
end
|
19
|
+
|
20
|
+
def branch
|
21
|
+
attributes.head.ref
|
22
|
+
end
|
23
|
+
|
24
|
+
def in_progress?
|
25
|
+
label_names.any? { |label| label.match(/(WIP|REVIEW)/) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def label_names
|
29
|
+
labels.map(&:name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def url
|
33
|
+
attributes.rels[:html].href
|
34
|
+
end
|
35
|
+
|
36
|
+
def created_at
|
37
|
+
attributes.created_at
|
38
|
+
end
|
39
|
+
|
40
|
+
def updated_at
|
41
|
+
attributes.updated_at
|
42
|
+
end
|
43
|
+
|
44
|
+
def author
|
45
|
+
@author ||= attributes.user.rels[:self].get.data.name
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def prefetch_data
|
50
|
+
author
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Startling
|
2
|
+
module Github
|
3
|
+
class Repo
|
4
|
+
attr_reader :api, :name
|
5
|
+
|
6
|
+
def initialize(name, api)
|
7
|
+
@name = name
|
8
|
+
@api = api
|
9
|
+
end
|
10
|
+
|
11
|
+
def attributes
|
12
|
+
@attributes ||= api.repository_attributes(name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_branch
|
16
|
+
attributes.default_branch
|
17
|
+
end
|
18
|
+
|
19
|
+
def pull_requests
|
20
|
+
@pull_requests ||= api.pull_requests(name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def pull_request(branch)
|
24
|
+
pull_requests.find { |pr| pr.branch == branch }
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_labels_for_issue(issue_id:, labels:)
|
28
|
+
labels = Array(labels)
|
29
|
+
api.set_labels_for_issue(repo_name: name, issue_id: issue_id, labels: labels)
|
30
|
+
end
|
31
|
+
|
32
|
+
def open_pull_request(title: nil, body: nil, branch: nil)
|
33
|
+
pull_request = api.open_pull_request(repo_name: name, destination_branch: default_branch,
|
34
|
+
branch: branch, title: title, body: body)
|
35
|
+
return pull_request if pull_request
|
36
|
+
pull_request(branch) # In case this is already open
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.all
|
40
|
+
@all ||= Startling::REPOS.map { |name| new(name) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative "pull_request_handler_base"
|
2
|
+
|
3
|
+
module Startling
|
4
|
+
module Handlers
|
5
|
+
class DefaultPullRequestHandler < PullRequestHandlerBase
|
6
|
+
def title
|
7
|
+
title = ask("Please input a pull request title: ")
|
8
|
+
abort "Title must be specified." if title.empty?
|
9
|
+
title
|
10
|
+
end
|
11
|
+
|
12
|
+
def body
|
13
|
+
Startling.pull_request_body
|
14
|
+
end
|
15
|
+
|
16
|
+
def commit_message
|
17
|
+
Startling.pull_request_commit_message
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Startling
|
2
|
+
module Handlers
|
3
|
+
class PullRequestHandlerBase
|
4
|
+
def initialize(args)
|
5
|
+
@args = args
|
6
|
+
end
|
7
|
+
|
8
|
+
def title
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
def body
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
def commit_message
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.run_all_when_everything_filtered = true
|
9
|
+
config.filter_run :focus
|
10
|
+
|
11
|
+
# Run specs in random order to surface order dependencies. If you find an
|
12
|
+
# order dependency and want to debug it, you can fix the order by providing
|
13
|
+
# the seed, which is printed after each run.
|
14
|
+
# --seed 1234
|
15
|
+
config.order = 'random'
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'pry'
|
19
|
+
Dir['./spec/support/**/*.rb'].map {|f| require f}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'startling/commands/base'
|
4
|
+
|
5
|
+
describe Startling::Commands::Base do
|
6
|
+
it "should assigns key/values passed in as attributes" do
|
7
|
+
base = Startling::Commands::Base.new(foo: 'bar', baz: 'qux')
|
8
|
+
expect(base.foo).to eq 'bar'
|
9
|
+
expect(base.baz).to eq 'qux'
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#execute' do
|
13
|
+
subject { Startling::Commands::Base.new }
|
14
|
+
it 'should throws NotImplementedError' do
|
15
|
+
expect{ subject.execute }.to raise_error(NotImplementedError)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#run' do
|
20
|
+
subject { Startling::Commands::Base.run(foo: 'bar')}
|
21
|
+
|
22
|
+
it 'should assign attributes and call execute' do
|
23
|
+
base = double :base_command
|
24
|
+
allow(Startling::Commands::Base).to receive(:new).and_return(base)
|
25
|
+
|
26
|
+
expect(Startling::Commands::Base).to receive(:new).with(foo: 'bar')
|
27
|
+
expect(base).to receive(:execute)
|
28
|
+
|
29
|
+
subject
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'startling'
|
3
|
+
|
4
|
+
module Startling
|
5
|
+
describe Configuration do
|
6
|
+
let(:configuration) { Configuration.new }
|
7
|
+
|
8
|
+
describe "Default settings" do
|
9
|
+
it "sets the default cache_dir to pwd" do
|
10
|
+
expect(configuration.cache_dir).to eql(Dir.pwd)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "sets the default root_dir to pwd" do
|
14
|
+
expect(configuration.root_dir).to eql(Dir.pwd)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "sets the default wip limit to WIP_LIMIT" do
|
18
|
+
expect(configuration.wip_limit)
|
19
|
+
.to eql(Configuration::DEFAULT_WIP_LIMIT)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "sets the default repos to empty" do
|
23
|
+
expect(configuration.repos).to eql([])
|
24
|
+
end
|
25
|
+
|
26
|
+
it "sets the default pull request commit message" do
|
27
|
+
expect(configuration.pull_request_commit_message)
|
28
|
+
.to eql(Configuration::DEFAULT_COMMIT_MESSAGE)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "sets the default pull request labels" do
|
32
|
+
expect(configuration.pull_request_labels).to eql([])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#cache_dir" do
|
37
|
+
it "can set the value" do
|
38
|
+
configuration.cache_dir = "new dir"
|
39
|
+
expect(configuration.cache_dir).to eql("new dir")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#root_dir" do
|
44
|
+
it "can set the value" do
|
45
|
+
configuration.root_dir = "new dir"
|
46
|
+
expect(configuration.root_dir).to eql("new dir")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#wip_limit" do
|
51
|
+
it "can set the value" do
|
52
|
+
configuration.wip_limit = 6
|
53
|
+
expect(configuration.wip_limit).to eql(6)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#repos" do
|
58
|
+
it "can set the value" do
|
59
|
+
configuration.repos << "repo path"
|
60
|
+
expect(configuration.repos).to eql(["repo path"])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#pull_request_commit_message" do
|
65
|
+
it "can set the value" do
|
66
|
+
configuration.pull_request_commit_message = "The Commit"
|
67
|
+
expect(configuration.pull_request_commit_message).to eql("The Commit")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#pull_request_labels" do
|
72
|
+
it "can set the value" do
|
73
|
+
configuration.pull_request_labels = ["WIP", "REVIEW"]
|
74
|
+
expect(configuration.pull_request_labels).to eql(["WIP", "REVIEW"])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe ".load_configuration" do
|
79
|
+
let(:git) { double(GitLocal) }
|
80
|
+
let(:config_files) {
|
81
|
+
{
|
82
|
+
caps: "Startlingfile.rb",
|
83
|
+
lower: "startlingfile.rb"
|
84
|
+
}
|
85
|
+
}
|
86
|
+
let(:caps_config_file) { "Startlingfile.rb" }
|
87
|
+
let(:lower_config_file) { "startlingfile.rb" }
|
88
|
+
|
89
|
+
before do
|
90
|
+
allow(Startling::GitLocal).to receive(:new) { git }
|
91
|
+
allow(git).to receive(:project_root) { Dir.pwd }
|
92
|
+
end
|
93
|
+
|
94
|
+
after do
|
95
|
+
config_files.each do |_, v|
|
96
|
+
delete_config_file(v)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "when no configuration file exists" do
|
101
|
+
it "uses default configuration" do
|
102
|
+
expect(Configuration.load_configuration).to eql(nil)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "when a configuration file exists" do
|
107
|
+
it "loads configuration from Startlingfile.rb" do
|
108
|
+
create_config_file(config_files[:caps])
|
109
|
+
expect(Configuration.load_configuration).to eql(config_files[:caps])
|
110
|
+
end
|
111
|
+
|
112
|
+
it "loads configuration from startlingfile.rb" do
|
113
|
+
create_config_file(config_files[:lower])
|
114
|
+
expect(Configuration.load_configuration).to eql(config_files[:lower])
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def create_config_file(config_file)
|
120
|
+
File.open(config_file, "w")
|
121
|
+
end
|
122
|
+
|
123
|
+
def delete_config_file(config_file)
|
124
|
+
File.delete(config_file) if File.exists? config_file
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'startling/git_local'
|
3
|
+
|
4
|
+
module Startling
|
5
|
+
describe GitLocal do
|
6
|
+
subject { GitLocal.new }
|
7
|
+
|
8
|
+
it "parses the repo name out of an https url" do
|
9
|
+
url = 'https://github.com/TeachingChannel/startling.git'
|
10
|
+
allow(subject).to receive(:remote_url) { url }
|
11
|
+
|
12
|
+
expect(subject.repo_name).to eq('TeachingChannel/startling')
|
13
|
+
end
|
14
|
+
|
15
|
+
it "parses the repo name out of an ssh url" do
|
16
|
+
url = 'git@github.com:TeachingChannel/startling.git'
|
17
|
+
allow(subject).to receive(:remote_url) { url }
|
18
|
+
|
19
|
+
expect(subject.repo_name).to eq('TeachingChannel/startling')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'startling/github/pull_request'
|
3
|
+
|
4
|
+
module Startling
|
5
|
+
module Github
|
6
|
+
describe PullRequest, "#in_progress?" do
|
7
|
+
|
8
|
+
it "should be true for WIP label" do
|
9
|
+
pull_request = PullRequest.new(double(:attributes))
|
10
|
+
allow(pull_request).to receive(:label_names) { ["WIP"] }
|
11
|
+
|
12
|
+
expect(pull_request.in_progress?).to be_truthy
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should be true for REVIEW label" do
|
16
|
+
pull_request = PullRequest.new(double(:attributes))
|
17
|
+
allow(pull_request).to receive(:label_names) { ["REVIEW"] }
|
18
|
+
|
19
|
+
expect(pull_request.in_progress?).to be_truthy
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be false if for other labels" do
|
23
|
+
pull_request = PullRequest.new(double(:attributes))
|
24
|
+
allow(pull_request).to receive(:label_names) { ["some other thing", "bug"] }
|
25
|
+
|
26
|
+
expect(pull_request.in_progress?).to be_falsey
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should be false if no labels" do
|
30
|
+
pull_request = PullRequest.new(double(:attributes))
|
31
|
+
allow(pull_request).to receive(:label_names) { [] }
|
32
|
+
|
33
|
+
expect(pull_request.in_progress?).to be_falsey
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'startling'
|
3
|
+
|
4
|
+
module Startling
|
5
|
+
describe ".configure" do
|
6
|
+
before do
|
7
|
+
Startling.configure do |config|
|
8
|
+
config.cache_dir = "value"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "sets the configuration attributes" do
|
13
|
+
expect(Startling.cache_dir).to eql("value")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'startling'
|
4
|
+
require 'startling/git_local'
|
5
|
+
require 'startling/github'
|
6
|
+
|
7
|
+
describe "bin/start" do
|
8
|
+
let(:feature_name) { 'bin_start_starts_stories' }
|
9
|
+
let(:feature_branch) { "feature/#{feature_name}" }
|
10
|
+
let(:repo_default_branch) { 'master' }
|
11
|
+
let(:pull_request_body) { "This is a test body" }
|
12
|
+
let(:git) { Startling::GitLocal.new }
|
13
|
+
let(:repo) { Startling::Github.repo(git.repo_name) }
|
14
|
+
let(:pull_request) { repo.pull_request(feature_branch) }
|
15
|
+
|
16
|
+
before do
|
17
|
+
local_configuration = <<CONFIG
|
18
|
+
Startling.configure do |config|
|
19
|
+
# WIP Limit
|
20
|
+
# config.wip_limit = 4
|
21
|
+
|
22
|
+
# Repos to check against for WIP limit
|
23
|
+
# config.repos << 'substantial/startling-dev'
|
24
|
+
|
25
|
+
# Valid story estimations
|
26
|
+
# config.valid_estimates = [1, 2, 4, 8, 16, 32, 64, 128]
|
27
|
+
|
28
|
+
# Commands to be run before a story is stared
|
29
|
+
# config.hook_commands.before_story_start = [:check_wip]
|
30
|
+
|
31
|
+
# Command to be run after a story has started
|
32
|
+
# config.hook_commands.after_story_start = []
|
33
|
+
|
34
|
+
# Commands to be run before a pull request is created
|
35
|
+
# config.hook_commands.before_pull_request = []
|
36
|
+
|
37
|
+
# Commands to be run after a pull request is created
|
38
|
+
# config.hook_commands.after_pull_request = []
|
39
|
+
|
40
|
+
# Handler used to start a provider specific story related to the pull request
|
41
|
+
# config.story_handler = :pivotal_start
|
42
|
+
|
43
|
+
# Message for pull request commit
|
44
|
+
# config.pull_request_commit_message = "Startling"
|
45
|
+
|
46
|
+
# Message for pull request body
|
47
|
+
config.pull_request_body = "#{pull_request_body}"
|
48
|
+
|
49
|
+
# Labels for a pull request
|
50
|
+
# config.pull_request_labels = [WIP, REVIEW, HOLD]
|
51
|
+
|
52
|
+
# Handler used for setting the title and body of a pull request
|
53
|
+
config.pull_request_handler = :default_pull_request_handler
|
54
|
+
end
|
55
|
+
CONFIG
|
56
|
+
|
57
|
+
File.open('startlingfile.rb', 'w') { |file| file.write(local_configuration) }
|
58
|
+
|
59
|
+
Startling::Commands::Base.load_configuration
|
60
|
+
|
61
|
+
test_repo_path = "tmp/test_repo"
|
62
|
+
FileUtils.mkdir_p "tmp"
|
63
|
+
|
64
|
+
unless File.exists? "tmp/test_repo/.git"
|
65
|
+
`git clone git@github.com:substantial/startling-testing.git #{test_repo_path}`
|
66
|
+
end
|
67
|
+
|
68
|
+
File.write File.join(test_repo_path, ".github_access_token"), Tokens.github
|
69
|
+
|
70
|
+
FileUtils.cd test_repo_path
|
71
|
+
|
72
|
+
Startling.root_dir = Startling.cache_dir = "."
|
73
|
+
|
74
|
+
git.checkout_branch repo_default_branch
|
75
|
+
git.destroy_branch feature_branch
|
76
|
+
end
|
77
|
+
|
78
|
+
after do
|
79
|
+
FileUtils.cd "#{__dir__}/.."
|
80
|
+
FileUtils.rm "startlingfile.rb"
|
81
|
+
end
|
82
|
+
|
83
|
+
# VCR Preconditions:
|
84
|
+
# * There should be no open pull requests:
|
85
|
+
# https://github.com/substantial/startling-testing
|
86
|
+
|
87
|
+
it "starts stories from origin/master",
|
88
|
+
vcr: { cassette_name: "bin_start_starts_stories" } do
|
89
|
+
|
90
|
+
allow_any_instance_of(Startling::Handlers::DefaultPullRequestHandler)
|
91
|
+
.to receive(:ask)
|
92
|
+
.with("Please input a pull request title: ") { "The Title" }
|
93
|
+
|
94
|
+
allow_any_instance_of(Startling::Commands::CreateBranch)
|
95
|
+
.to receive(:ask)
|
96
|
+
.with("Enter branch name (enter for current branch): ") { feature_name }
|
97
|
+
|
98
|
+
command = Startling::Command.new(args: [])
|
99
|
+
command.execute
|
100
|
+
|
101
|
+
expect(git.remote_branches).to include feature_branch
|
102
|
+
expect(git.current_branch).to eq feature_branch
|
103
|
+
expect(repo.default_branch).to eq repo_default_branch
|
104
|
+
end
|
105
|
+
|
106
|
+
it "The pull request body is the same as the config",
|
107
|
+
vcr: { cassette_name: "bin_start_starts_stories_pr_body" } do
|
108
|
+
|
109
|
+
allow_any_instance_of(Startling::Handlers::DefaultPullRequestHandler)
|
110
|
+
.to receive(:ask)
|
111
|
+
.with("Please input a pull request title: ") { "The Title" }
|
112
|
+
|
113
|
+
allow_any_instance_of(Startling::Commands::CreateBranch)
|
114
|
+
.to receive(:ask)
|
115
|
+
.with("Enter branch name (enter for current branch): ") { feature_name }
|
116
|
+
|
117
|
+
command = Startling::Command.new(args: [])
|
118
|
+
command.execute
|
119
|
+
|
120
|
+
expect(pull_request.attributes.body).to eq pull_request_body
|
121
|
+
end
|
122
|
+
end
|