git-pivotal-tracker-centro 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/.travis.yml +3 -0
- data/CHANGELOG +54 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +82 -0
- data/LICENSE +21 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/bin/git-finish +7 -0
- data/bin/git-info +7 -0
- data/bin/git-start +8 -0
- data/features/finish.feature +88 -0
- data/features/info.feature +99 -0
- data/features/start.feature +113 -0
- data/features/step_definitions/steps.rb +119 -0
- data/features/support/dsl/assertions.rb +11 -0
- data/features/support/dsl/data.rb +11 -0
- data/features/support/dsl/pivotal.rb +77 -0
- data/features/support/env.rb +6 -0
- data/features/support/git-pivotal.rb +68 -0
- data/features/test_repo/origin.git/COMMIT_EDITMSG +1 -0
- data/features/test_repo/origin.git/HEAD +1 -0
- data/features/test_repo/origin.git/config +8 -0
- data/features/test_repo/origin.git/description +1 -0
- data/features/test_repo/origin.git/hooks/applypatch-msg.sample +15 -0
- data/features/test_repo/origin.git/hooks/commit-msg.sample +24 -0
- data/features/test_repo/origin.git/hooks/post-commit.sample +8 -0
- data/features/test_repo/origin.git/hooks/post-receive.sample +15 -0
- data/features/test_repo/origin.git/hooks/post-update.sample +8 -0
- data/features/test_repo/origin.git/hooks/pre-applypatch.sample +14 -0
- data/features/test_repo/origin.git/hooks/pre-commit.sample +46 -0
- data/features/test_repo/origin.git/hooks/pre-rebase.sample +169 -0
- data/features/test_repo/origin.git/hooks/prepare-commit-msg.sample +36 -0
- data/features/test_repo/origin.git/hooks/update.sample +128 -0
- data/features/test_repo/origin.git/index +0 -0
- data/features/test_repo/origin.git/info/exclude +6 -0
- data/features/test_repo/origin.git/logs/HEAD +1 -0
- data/features/test_repo/origin.git/logs/refs/heads/master +1 -0
- data/features/test_repo/origin.git/objects/0c/6f7b1384910d1a2f137590095f008a06c7e00c +0 -0
- data/features/test_repo/origin.git/objects/10/ecf2b7ce989f01f3f7266e712b48d9275f2635 +0 -0
- data/features/test_repo/origin.git/objects/a5/71d56305df09fb060f6ccb730b46080d305beb +0 -0
- data/features/test_repo/origin.git/refs/heads/master +1 -0
- data/features/test_repo/readme +1 -0
- data/git-pivotal-tracker-centro.gemspec +29 -0
- data/lib/commands/base.rb +120 -0
- data/lib/commands/bug.rb +19 -0
- data/lib/commands/card.rb +32 -0
- data/lib/commands/chore.rb +19 -0
- data/lib/commands/feature.rb +19 -0
- data/lib/commands/finish.rb +67 -0
- data/lib/commands/info.rb +58 -0
- data/lib/commands/map.rb +10 -0
- data/lib/commands/pick.rb +94 -0
- data/lib/commands/start.rb +39 -0
- data/lib/git-pivotal-tracker.rb +11 -0
- data/readme.markdown +106 -0
- data/spec/commands/base_spec.rb +137 -0
- data/spec/commands/bug_spec.rb +24 -0
- data/spec/commands/chore_spec.rb +24 -0
- data/spec/commands/feature_spec.rb +24 -0
- data/spec/commands/finish_spec.rb +134 -0
- data/spec/commands/map_spec.rb +14 -0
- data/spec/commands/start_spec.rb +28 -0
- data/spec/factories.rb +13 -0
- data/spec/factory.rb +26 -0
- data/spec/spec_helper.rb +24 -0
- metadata +291 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commands::Base do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@input = mock('input')
|
7
|
+
@output = mock('output')
|
8
|
+
|
9
|
+
# stub out git config requests
|
10
|
+
Commands::Base.any_instance.stubs(:get).with { |v| v =~ /git config/ }.returns("")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should set the api key with the -k option" do
|
14
|
+
@pick = Commands::Base.new("-k", "1234").with(@input, @output)
|
15
|
+
@pick.options[:api_token].should == "1234"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should set the api key with the --api-token= option" do
|
19
|
+
@pick = Commands::Base.new("--api-key=1234").with(@input, @output)
|
20
|
+
@pick.options[:api_token].should == "1234"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should set the project id with the -p option" do
|
24
|
+
@pick = Commands::Base.new("-p", "1").with(@input, @output)
|
25
|
+
@pick.options[:project_id].should == "1"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should set the project id with the --project-id= option" do
|
29
|
+
@pick = Commands::Base.new("--project-id=1").with(@input, @output)
|
30
|
+
@pick.options[:project_id].should == "1"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should set the full name with the -n option" do
|
34
|
+
@pick = Commands::Base.new("-n", "Jeff Tucker").with(@input, @output)
|
35
|
+
@pick.options[:full_name].should == "Jeff Tucker"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should set the full name with the --full-name= option" do
|
39
|
+
@pick = Commands::Base.new(@input, @output,"--full-name=Jeff Tucker")
|
40
|
+
@pick.options[:full_name].should == "Jeff Tucker"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should set the quiet flag with the -q option" do
|
44
|
+
@pick = Commands::Base.new("-q").with(@input, @output)
|
45
|
+
@pick.options[:quiet].should be_true
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should set the quiet flag with the --quiet option" do
|
49
|
+
@pick = Commands::Base.new("--quiet").with(@input, @output)
|
50
|
+
@pick.options[:quiet].should be_true
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should set the force flag with the -f option" do
|
54
|
+
@pick = Commands::Base.new("-f").with(@input, @output)
|
55
|
+
@pick.options[:force].should be_true
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should set the force flag with the --force option" do
|
59
|
+
@pick = Commands::Base.new("--force").with(@input, @output)
|
60
|
+
@pick.options[:force].should be_true
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should set the verbose flag with the -v option" do
|
64
|
+
@pick = Commands::Base.new("-v").with(@input, @output)
|
65
|
+
@pick.options[:verbose].should be_true
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should set the verbose flag with the --verbose option" do
|
69
|
+
@pick = Commands::Base.new("--verbose").with(@input, @output)
|
70
|
+
@pick.options[:verbose].should be_true
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should unset the verbose flag with the --no-verbose option" do
|
74
|
+
@pick = Commands::Base.new("--no-verbose").with(@input, @output)
|
75
|
+
@pick.options[:verbose].should be_false
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should respect verbose from git config if it's set true (case insensitive)" do
|
79
|
+
Commands::Base.any_instance.stubs(:get).with("git config --get pivotal.verbose").returns("truE")
|
80
|
+
@pick = Commands::Base.new
|
81
|
+
@pick.options[:verbose].should be_true
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should respect verbose from git config if it's set true (case insensitive)" do
|
85
|
+
Commands::Base.any_instance.stubs(:get).with("git config --get pivotal.verbose").returns("falSe")
|
86
|
+
@pick = Commands::Base.new
|
87
|
+
@pick.options[:verbose].should be_false
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should be verbose by default" do
|
91
|
+
Commands::Base.any_instance.stubs(:get).with("git config --get pivotal.verbose").returns("")
|
92
|
+
@pick = Commands::Base.new
|
93
|
+
@pick.options[:verbose].should be_true
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should print a message if the API token is missing" do
|
97
|
+
@output.expects(:print).with("Pivotal Tracker API Token and Project ID are required\n")
|
98
|
+
|
99
|
+
@pick = Commands::Base.new("-p", "1").with(@input, @output)
|
100
|
+
@pick.run!
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should print a message if the project ID is missing" do
|
104
|
+
@output.expects(:print).with("Pivotal Tracker API Token and Project ID are required\n")
|
105
|
+
|
106
|
+
@pick = Commands::Base.new("-k", "1").with(@input, @output)
|
107
|
+
@pick.run!
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should set use_ssl to true with --use-ssl" do
|
111
|
+
@pick = Commands::Base.new("--use-ssl").with(@input, @output)
|
112
|
+
@pick.options[:use_ssl].should be_true
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should set use_ssl to true with -S" do
|
116
|
+
@pick = Commands::Base.new("-S").with(@input, @output)
|
117
|
+
@pick.options[:use_ssl].should be_true
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should set use_ssl to false by default" do
|
121
|
+
@pick = Commands::Base.new("").with(@input, @output)
|
122
|
+
@pick.options[:use_ssl].should be_false
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should respect use_ssl from git config if it's set true (case insensitive)" do
|
126
|
+
Commands::Base.any_instance.stubs(:get).with("git config --get pivotal.use-ssl").returns("truE")
|
127
|
+
@pick = Commands::Base.new
|
128
|
+
@pick.options[:use_ssl].should be_true
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should respect use_ssl from git config if it's set to anything but true" do
|
132
|
+
Commands::Base.any_instance.stubs(:get).with("git config --get pivotal.use-ssl").returns("not true")
|
133
|
+
@pick = Commands::Base.new
|
134
|
+
@pick.options[:use_ssl].should be_false
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commands::Bug do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
# stub out git config requests
|
7
|
+
Commands::Bug.any_instance.stubs(:get).with { |v| v =~ /git config/ }.returns("")
|
8
|
+
|
9
|
+
@bug = Commands::Bug.new
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should specify its story type" do
|
13
|
+
@bug.type.should == "bug"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should specify a plural for its story types" do
|
17
|
+
@bug.plural_type.should == "bugs"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should specify its branch suffix" do
|
21
|
+
@bug.branch_suffix.should == "bugfix"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commands::Chore do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
# stub out git config requests
|
7
|
+
Commands::Chore.any_instance.stubs(:get).with { |v| v =~ /git config/ }.returns("")
|
8
|
+
|
9
|
+
@chore = Commands::Chore.new
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should specify its story type" do
|
13
|
+
@chore.type.should == "chore"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should specify a plural for its story types" do
|
17
|
+
@chore.plural_type.should == "chores"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should specify its branch suffix" do
|
21
|
+
@chore.branch_suffix.should == "chore"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commands::Feature do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
# stub out git config requests
|
7
|
+
Commands::Feature.any_instance.stubs(:get).with { |v| v =~ /git config/ }.returns("")
|
8
|
+
|
9
|
+
@feature = Commands::Feature.new
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should specify its story type" do
|
13
|
+
@feature.type.should == "feature"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should specify a plural for its story types" do
|
17
|
+
@feature.plural_type.should == "features"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should specify its branch suffix" do
|
21
|
+
@feature.branch_suffix.should == "feature"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commands::Finish do
|
4
|
+
|
5
|
+
def mock_project_id
|
6
|
+
@mock_project_id ||= '4321'
|
7
|
+
end
|
8
|
+
|
9
|
+
def mock_projects
|
10
|
+
if @mock_projects.nil?
|
11
|
+
@mock_projects = mock("mock project")
|
12
|
+
@mock_projects.stubs(:stories).returns(mock_stories)
|
13
|
+
end
|
14
|
+
@mock_projects
|
15
|
+
end
|
16
|
+
|
17
|
+
def mock_stories
|
18
|
+
if @mock_stories.nil?
|
19
|
+
@mock_stories = mock("mock story list")
|
20
|
+
@mock_stories.stubs(:find).with(mock_story_id).returns(mock_story)
|
21
|
+
end
|
22
|
+
@mock_stories
|
23
|
+
end
|
24
|
+
|
25
|
+
def mock_story_id
|
26
|
+
@mock_story_id ||= 1234
|
27
|
+
end
|
28
|
+
|
29
|
+
def mock_story
|
30
|
+
if @mock_story.nil?
|
31
|
+
@mock_story = mock("mock story")
|
32
|
+
end
|
33
|
+
@mock_story
|
34
|
+
end
|
35
|
+
|
36
|
+
def branch_name
|
37
|
+
"#{mock_story_id}-feature-branch-name"
|
38
|
+
end
|
39
|
+
|
40
|
+
before(:each) do
|
41
|
+
# stub out git config requests
|
42
|
+
Commands::Finish.any_instance.stubs(:get).with { |v| v =~ /git config/ }.returns("")
|
43
|
+
|
44
|
+
PivotalTracker::Project.stubs(:find).returns(mock_projects)
|
45
|
+
|
46
|
+
@finish = Commands::Finish.new(nil, nil, "-p", mock_project_id)
|
47
|
+
@finish.stubs(:put)
|
48
|
+
@finish.stubs(:get_char).returns('y')
|
49
|
+
end
|
50
|
+
|
51
|
+
context "where the branch name does not contain a valid story id" do
|
52
|
+
before(:each) do
|
53
|
+
# stub out git status request to identify the branch
|
54
|
+
branch_name = "invalid-branch-name-without-story-id"
|
55
|
+
Commands::Finish.any_instance.stubs(:get).with { |v| v =~ /git status/ }.returns("# On branch #{branch_name}")
|
56
|
+
Commands::Finish.any_instance.stubs(:get).with { |v| v == "git symbolic-ref HEAD" }.returns(branch_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should return an exit status of one" do
|
60
|
+
@finish.run!.should == 1
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should print an error message" do
|
64
|
+
@finish.expects(:put).with("Branch name must contain a Pivotal Tracker story id").once
|
65
|
+
@finish.run!
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "where the branch name does contain a valid story id" do
|
70
|
+
before(:each) do
|
71
|
+
# stub out git status request to identify the branch
|
72
|
+
Commands::Finish.any_instance.stubs(:get).with { |v| v =~ /git status/ }.returns("# On branch #{branch_name}")
|
73
|
+
Commands::Finish.any_instance.stubs(:get).with { |v| v == "git symbolic-ref HEAD" }.returns(branch_name)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should exit if the user does not wish to proceed" do
|
77
|
+
@finish.stubs(:get_char).returns('n')
|
78
|
+
@finish.expects(:sys).never
|
79
|
+
@finish.run!
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should checkout the master branch, delete the local branch, and delete the remote branch" do
|
83
|
+
@finish.expects(:sys).with() { |value| value == "git checkout master" }
|
84
|
+
@finish.expects(:sys).with("git branch -d #{branch_name}").returns(true)
|
85
|
+
@finish.expects(:sys).with("git push origin :#{branch_name}")
|
86
|
+
mock_story.stubs(:story_type).returns("finished")
|
87
|
+
mock_story.stubs(:update)
|
88
|
+
@finish.run!
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should attempt to update the story status to the stories finished_state" do
|
92
|
+
@finish.stubs(:sys).returns(true)
|
93
|
+
mock_story.stubs(:story_type).returns("finished")
|
94
|
+
mock_story.expects(:update).with(:current_state => "finished")
|
95
|
+
@finish.run!
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should not delete the remote branch and update the story if the local branch could not be deleted" do
|
99
|
+
@finish.expects(:sys).with() { |value| value == "git checkout master" }
|
100
|
+
@finish.expects(:sys).with("git branch -d #{branch_name}").returns(false)
|
101
|
+
@finish.expects(:sys).with("git push origin :#{branch_name}").never
|
102
|
+
@finish.run!
|
103
|
+
end
|
104
|
+
|
105
|
+
context "and the story is successfully marked as finished in PT" do
|
106
|
+
before(:each) do
|
107
|
+
@finish.stubs(:sys).returns(true)
|
108
|
+
mock_story.stubs(:story_type).returns("finished")
|
109
|
+
mock_story.stubs(:update).with(:current_state => "finished").returns(true)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should succeed with an exist status of zero" do
|
113
|
+
@finish.run!.should == 0
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "and the story fails to be marked as finished in PT" do
|
118
|
+
before(:each) do
|
119
|
+
@finish.stubs(:sys).returns(true)
|
120
|
+
mock_story.stubs(:story_type).returns("finished")
|
121
|
+
mock_story.stubs(:update).with(:current_state => "finished").returns(false)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should fail with an exist status of one" do
|
125
|
+
@finish.run!.should == 1
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should print an error message" do
|
129
|
+
@finish.expects(:put).with("Unable to mark Story #{mock_story_id} as finished").once
|
130
|
+
@finish.run!
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commands::Map do
|
4
|
+
|
5
|
+
describe "#[]" do
|
6
|
+
it "retrieves values when a specific key matches a regular expression" do
|
7
|
+
subject[/^\d+$/] = "w00t"
|
8
|
+
subject["1234"].should eq("w00t")
|
9
|
+
subject["9123423423"].should eq("w00t")
|
10
|
+
subject["a9123423423"].should be_nil
|
11
|
+
subject["1234f"].should be_nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commands::Start do
|
4
|
+
|
5
|
+
describe '.for' do
|
6
|
+
it "returns the Command::Bug when the first argument is bug" do
|
7
|
+
Commands::Start.for("bug").should be_instance_of(Commands::Bug)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "returns the Command::Chore when the first argument is chore" do
|
11
|
+
Commands::Start.for("chore").should be_instance_of(Commands::Chore)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "returns the Command::Feature when the first argument is feature" do
|
15
|
+
Commands::Start.for("feature").should be_instance_of(Commands::Feature)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "returns the Command::Card when the first argument is a card id" do
|
19
|
+
Commands::Start.for("123456").should be_instance_of(Commands::Card)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "raises when the command is unknown card type" do
|
23
|
+
Commands::Start.expects(:display_usage_instructions_and_quit)
|
24
|
+
Commands::Start.for("unknown")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/spec/factories.rb
ADDED
data/spec/factory.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
class Factory
|
2
|
+
class << self
|
3
|
+
def factories
|
4
|
+
@@factories ||= {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def define(type, attributes)
|
8
|
+
factories[type] = attributes
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def Factory(type, attrs = {})
|
15
|
+
defaults = Factory.factories[type]
|
16
|
+
attrs = defaults.merge(attrs)
|
17
|
+
|
18
|
+
markup = Builder::XmlMarkup.new
|
19
|
+
markup.__send__(type) do
|
20
|
+
attrs.each do |attribute, value|
|
21
|
+
markup.__send__(attribute, value.to_s)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
markup.target!
|
26
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'mocha'
|
2
|
+
require 'builder'
|
3
|
+
|
4
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
5
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
6
|
+
# Require this file using `require "spec_helper.rb"` to ensure that it is only
|
7
|
+
# loaded once.
|
8
|
+
#
|
9
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.mock_with :mocha
|
12
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
13
|
+
config.run_all_when_everything_filtered = true
|
14
|
+
config.filter_run :focus
|
15
|
+
end
|
16
|
+
|
17
|
+
specdir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
18
|
+
require File.join(specdir, 'lib','git-pivotal-tracker')
|
19
|
+
|
20
|
+
require File.join(File.dirname(__FILE__), 'factories')
|
21
|
+
|
22
|
+
def stub_connection_to_pivotal
|
23
|
+
RestClient::Resource.any_instance.stubs(:get).returns("")
|
24
|
+
end
|