bitcoin 0.1.0 → 0.1.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.
- data/.rvmrc +1 -1
- data/Gemfile.lock +56 -4
- data/Guardfile +38 -0
- data/README.markdown +21 -2
- data/bin/rbcoin +8 -1
- data/bitcoin.gemspec +17 -5
- data/config/cucumber.yml +3 -3
- data/config/darcs.boring +121 -0
- data/doc/DEFINITION_OF_DONE.markdown +12 -0
- data/doc/HISTORY.markdown +19 -0
- data/{LICENCE.markdown → doc/LICENCE.markdown} +1 -1
- data/doc/TODO.markdown +31 -0
- data/doc/UBIQUITOUS_LANGUAGE.markdown +15 -0
- data/features/descriptions/command_help.feature +31 -0
- data/features/descriptions/satoshi_wallet/add_address.feature +49 -0
- data/features/descriptions/satoshi_wallet/show_addresses.feature +18 -0
- data/features/descriptions/satoshi_wallet/show_version.feature +17 -0
- data/features/descriptions/satoshi_wallet/subcommand_help.feature +20 -0
- data/features/fixtures/ABOUT_FIXTURES.markdown +6 -0
- data/features/fixtures/addressbook_wallet.dat +0 -0
- data/features/fixtures/new_wallet.dat +0 -0
- data/features/step_definitions/command_steps.rb +3 -0
- data/features/step_definitions/wallet_steps.rb +11 -0
- data/features/support/env.rb +8 -1
- data/lib/bitcoin/cli.rb +35 -0
- data/lib/bitcoin/commands.rb +3 -0
- data/lib/bitcoin/commands/help_command.rb +32 -0
- data/lib/bitcoin/commands/satoshi_wallet.rb +11 -0
- data/lib/bitcoin/commands/satoshi_wallet/add_address_command.rb +61 -0
- data/lib/bitcoin/commands/satoshi_wallet/show_addresses_command.rb +16 -0
- data/lib/bitcoin/commands/satoshi_wallet/show_version_command.rb +15 -0
- data/lib/bitcoin/commands/satoshi_wallet_command.rb +37 -0
- data/lib/bitcoin/commands/satoshi_wallet_command_environment.rb +28 -0
- data/lib/bitcoin/console/capturing_stream_bundle.rb +42 -0
- data/lib/bitcoin/console/stream_bundle.rb +21 -0
- data/lib/bitcoin/data_access/satoshi/bdb_satoshi_wallet_repository.rb +155 -0
- data/lib/bitcoin/data_access/satoshi/satoshi_version.rb +58 -0
- data/lib/bitcoin/data_access/satoshi/satoshi_wallet.rb +39 -0
- data/lib/bitcoin/domain/address_book.rb +19 -0
- data/lib/bitcoin/domain/bitcoin_address.rb +33 -0
- data/lib/bitcoin/filesystem/empty_temp_dir.rb +74 -0
- data/lib/bitcoin/rspec/argument_matchers.rb +1 -0
- data/lib/bitcoin/rspec/argument_matchers/block_evaluating_to_matcher.rb +23 -0
- data/lib/bitcoin/rspec/directory_helpers.rb +22 -0
- data/lib/bitcoin/version.rb +1 -1
- data/spec/bitcoin/cli_spec.rb +128 -0
- data/spec/bitcoin/commands/help_command_spec.rb +53 -0
- data/spec/bitcoin/commands/satoshi_wallet/add_address_command_spec.rb +149 -0
- data/spec/bitcoin/commands/satoshi_wallet/show_addresses_command_spec.rb +26 -0
- data/spec/bitcoin/commands/satoshi_wallet/show_version_command_spec.rb +26 -0
- data/spec/bitcoin/commands/satoshi_wallet_command_environment_spec.rb +76 -0
- data/spec/bitcoin/commands/satoshi_wallet_command_spec.rb +73 -0
- data/spec/bitcoin/console/_contracts/stream_bundle_contract.rb +29 -0
- data/spec/bitcoin/console/capturing_stream_bundle_spec.rb +74 -0
- data/spec/bitcoin/console/stream_bundle_spec.rb +13 -0
- data/spec/bitcoin/data_access/satoshi/bdb_satoshi_wallet_repository_spec.rb +78 -0
- data/spec/bitcoin/data_access/satoshi/satoshi_version_spec.rb +112 -0
- data/spec/bitcoin/data_access/satoshi/satoshi_wallet_spec.rb +102 -0
- data/spec/bitcoin/domain/address_book_spec.rb +63 -0
- data/spec/bitcoin/domain/bitcoin_address_spec.rb +52 -0
- data/spec/bitcoin/filesystem/empty_temp_dir_spec.rb +170 -0
- data/spec/bitcoin/rspec/argument_matchers/block_evaluating_to_matcher_spec.rb +36 -0
- data/spec/spec_helper.rb +29 -1
- metadata +221 -18
@@ -0,0 +1,49 @@
|
|
1
|
+
Feature: satoshi-wallet add-address
|
2
|
+
In order to reduce the risk of using RbCoin
|
3
|
+
As a Bitcoin owner
|
4
|
+
I want to be able to add new Addresses into a Satoshi Wallet
|
5
|
+
|
6
|
+
Scenario: Add new address
|
7
|
+
Given a new Satoshi Wallet "new_wallet.dat"
|
8
|
+
When I successfully run `rbcoin satoshi-wallet add-address new_wallet.dat 14Z1mazY4HfysZyMaKudFr63EwHqQT2njz "Bitcoin Monitor"`
|
9
|
+
Then the output should contain "Address added successfully"
|
10
|
+
When I successfully run `rbcoin satoshi-wallet show-addresses new_wallet.dat`
|
11
|
+
Then the output should contain:
|
12
|
+
"""
|
13
|
+
# All addresses in new_wallet.dat
|
14
|
+
14Z1mazY4HfysZyMaKudFr63EwHqQT2njz Bitcoin Monitor
|
15
|
+
"""
|
16
|
+
|
17
|
+
Scenario: Invalid address
|
18
|
+
Given a new Satoshi Wallet "new_wallet.dat"
|
19
|
+
When I run `rbcoin satoshi-wallet add-address new_wallet.dat 1thisisaninvalidaddress "Bitcoin Monitor"`
|
20
|
+
Then the command should fail
|
21
|
+
And the stderr should contain:
|
22
|
+
"""
|
23
|
+
The address "1thisisaninvalidaddress" is not a valid Bitcoin address
|
24
|
+
The address was not added to the wallet
|
25
|
+
"""
|
26
|
+
|
27
|
+
Scenario: Missing arguments
|
28
|
+
Given a new Satoshi Wallet "new_wallet.dat"
|
29
|
+
When I run `rbcoin satoshi-wallet add-address new_wallet.dat 14Z1mazY4HfysZyMaKudFr63EwHqQT2njz`
|
30
|
+
Then the command should fail
|
31
|
+
And the stderr should contain:
|
32
|
+
"""
|
33
|
+
You must provide both a Bitcoin address and a name to add to a wallet
|
34
|
+
No changes were made to the wallet
|
35
|
+
"""
|
36
|
+
|
37
|
+
Scenario: Too many arguments
|
38
|
+
Given a new Satoshi Wallet "new_wallet.dat"
|
39
|
+
When I run `rbcoin satoshi-wallet add-address new_wallet.dat 14Z1mazY4HfysZyMaKudFr63EwHqQT2njz My name without quotes`
|
40
|
+
Then the command should fail
|
41
|
+
And the stderr should contain:
|
42
|
+
"""
|
43
|
+
Received too many arguments
|
44
|
+
|
45
|
+
To make sure the name is formatted correctly we ask you to use use quotes
|
46
|
+
around the name, e.g.: "My name without quotes"
|
47
|
+
|
48
|
+
No changes were made to the wallet
|
49
|
+
"""
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Feature: satoshi-wallet show-addresses
|
2
|
+
In order to enable easily transitioning from the Satoshi Client
|
3
|
+
As a Bitcoin owner
|
4
|
+
I want to inspect the contents of my Satoshi Wallet
|
5
|
+
|
6
|
+
Scenario: Viewing all addresses
|
7
|
+
# addressbook_wallet.dat is a wallet with some known addresses but no transactions
|
8
|
+
Given a new Satoshi Wallet "addressbook_wallet.dat"
|
9
|
+
When I successfully run `rbcoin satoshi-wallet show-addresses addressbook_wallet.dat`
|
10
|
+
# I don't know what drives the address book order yet - I think it's the address itself
|
11
|
+
Then the output should contain:
|
12
|
+
"""
|
13
|
+
# All addresses in addressbook_wallet.dat
|
14
|
+
13f2t2WKnCZh1rZbDhdUKB6fY1rZNWZVzd Receiving address 2
|
15
|
+
14Z1mazY4HfysZyMaKudFr63EwHqQT2njz Bitcoin Monitor
|
16
|
+
16kzGRdP8LToECecna3EynghiApUM9duLw Receiving address 1
|
17
|
+
1Nqr3MqVyUp6k3o3QPePAdn4Yg4tzgB9kw Bitcoincharts
|
18
|
+
"""
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Feature: satoshi-wallet show-version
|
2
|
+
In order to enable easily transitioning from the Satoshi Client
|
3
|
+
As a Bitcoin owner
|
4
|
+
I want to inspect the contents of my Satoshi Wallet
|
5
|
+
|
6
|
+
Scenario: Valid wallet file
|
7
|
+
# Which just so happens to be a 0.3.24 file...
|
8
|
+
Given a new Satoshi Wallet "new_wallet.dat"
|
9
|
+
When I successfully run `rbcoin satoshi-wallet show-version new_wallet.dat`
|
10
|
+
Then the output should contain "0.3.24"
|
11
|
+
|
12
|
+
Scenario: Missing wallet file
|
13
|
+
When I run `rbcoin satoshi-wallet show-version this_wallet_does_not_exist.dat`
|
14
|
+
Then it should fail with:
|
15
|
+
"""
|
16
|
+
Couldn't find wallet file: this_wallet_does_not_exist.dat
|
17
|
+
"""
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Feature: Subcommand help for `rbcoin satoshi-wallet`
|
2
|
+
In order to avoid being frustrated by being unable to interact with the application
|
3
|
+
As a user
|
4
|
+
I want to be told when I make a mistake
|
5
|
+
|
6
|
+
Scenario: `rbcoin satoshi-wallet unknown-subcommand`
|
7
|
+
When I run `rbcoin satoshi-wallet unknown-subcommand`
|
8
|
+
Then the command should fail
|
9
|
+
And the stderr should contain:
|
10
|
+
"""
|
11
|
+
Unknown satoshi-wallet subcommand: "unknown-subcommand"
|
12
|
+
"""
|
13
|
+
|
14
|
+
# Scenario: `rbcoin help satoshi-wallet`
|
15
|
+
# When I successfully run `rbcoin satoshi-wallet help`
|
16
|
+
# Then the output should contain "Usage: TODO"
|
17
|
+
#
|
18
|
+
# Scenario: `rbcoin satoshi-wallet --help`
|
19
|
+
# When I successfully run `rbcoin satoshi-wallet --help`
|
20
|
+
# Then the output should contain "Usage: TODO"
|
Binary file
|
Binary file
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Fixtures
|
2
|
+
def fixture_file_data(filename)
|
3
|
+
File.read(File.join(PROJECT_ROOT, "features", "fixtures", filename))
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
World(Fixtures)
|
8
|
+
|
9
|
+
Given %r/^a new Satoshi Wallet "([^"]*)"$/ do |wallet_filename|
|
10
|
+
write_file(wallet_filename, fixture_file_data(wallet_filename))
|
11
|
+
end
|
data/features/support/env.rb
CHANGED
@@ -1,2 +1,9 @@
|
|
1
1
|
require 'bundler'
|
2
|
-
Bundler.setup
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..", "..")).freeze
|
5
|
+
|
6
|
+
# So Aruba can find the binary
|
7
|
+
ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
|
8
|
+
|
9
|
+
require 'aruba/cucumber'
|
data/lib/bitcoin/cli.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'bitcoin/commands'
|
2
|
+
|
3
|
+
module Bitcoin
|
4
|
+
class CLI
|
5
|
+
class CommandError < RuntimeError; end
|
6
|
+
|
7
|
+
def initialize(stream_bundle)
|
8
|
+
@stream_bundle = stream_bundle
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(args)
|
12
|
+
command_name, *remaining_args = *args
|
13
|
+
dispatch_command(command_name, remaining_args)
|
14
|
+
rescue CommandError => error
|
15
|
+
1
|
16
|
+
else
|
17
|
+
0
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def dispatch_command(command_name, remaining_args)
|
23
|
+
case command_name
|
24
|
+
when nil, "help"
|
25
|
+
Commands::HelpCommand.new(@stream_bundle).show_help
|
26
|
+
when "satoshi-wallet"
|
27
|
+
command_environment = Commands::SatoshiWalletCommandEnvironment.new(@stream_bundle)
|
28
|
+
Commands::SatoshiWalletCommand.new(@stream_bundle, command_environment).run(remaining_args)
|
29
|
+
else
|
30
|
+
Commands::HelpCommand.new(@stream_bundle).show_unknown_command_help(command_name)
|
31
|
+
raise CommandError.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'lstrip-on-steroids'
|
2
|
+
|
3
|
+
module Bitcoin
|
4
|
+
module Commands
|
5
|
+
class HelpCommand
|
6
|
+
def initialize(stream_bundle)
|
7
|
+
@stream_bundle = stream_bundle
|
8
|
+
end
|
9
|
+
|
10
|
+
def show_help
|
11
|
+
@stream_bundle.puts_output(
|
12
|
+
-%{
|
13
|
+
Usage: rbcoin COMMAND ...
|
14
|
+
|
15
|
+
Commands:
|
16
|
+
help Display help about rbcoin and rbcoin commands
|
17
|
+
satoshi-wallet Manipulate the wallet.dat file of the original client
|
18
|
+
|
19
|
+
Use "rbcoin COMMAND --help" or "rbcoin help COMMAND" for help on a single command
|
20
|
+
Use "rbcoin --version" to see the rbcoin version number
|
21
|
+
Use "rbcoin --exact-version" to see the all version details (with dependencies)
|
22
|
+
}
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def show_unknown_command_help(command_name)
|
27
|
+
@stream_bundle.puts_output(%'rbcoin failed: No such command "#{command_name}"')
|
28
|
+
@stream_bundle.puts_output("Try: `rbcoin help`")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'bitcoin/cli' # For CommandError
|
2
|
+
|
3
|
+
require 'bitcoin/domain/bitcoin_address'
|
4
|
+
|
5
|
+
module Bitcoin
|
6
|
+
module Commands
|
7
|
+
module SatoshiWallet
|
8
|
+
class AddAddressCommand
|
9
|
+
def initialize(stream_bundle)
|
10
|
+
@stream_bundle = stream_bundle
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(wallet, options)
|
14
|
+
assert_valid_args(options[:args])
|
15
|
+
|
16
|
+
address, name = *options[:args]
|
17
|
+
|
18
|
+
assert_valid_name(name)
|
19
|
+
|
20
|
+
wallet.add_address(Domain::BitcoinAddress.new(address), name: name)
|
21
|
+
@stream_bundle.puts_output("Address added successfully")
|
22
|
+
rescue Domain::BitcoinAddress::InvalidBitcoinAddressError => error
|
23
|
+
@stream_bundle.puts_error(error.message)
|
24
|
+
@stream_bundle.puts_error("The address was not added to the wallet")
|
25
|
+
raise CLI::CommandError.new
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def assert_valid_args(args)
|
31
|
+
if args.length < 2
|
32
|
+
@stream_bundle.puts_error("You must provide both a Bitcoin address and a name to add to a wallet")
|
33
|
+
@stream_bundle.puts_error("No changes were made to the wallet")
|
34
|
+
raise CLI::CommandError.new
|
35
|
+
elsif args.length > 2
|
36
|
+
@stream_bundle.puts_error(
|
37
|
+
-%{
|
38
|
+
Received too many arguments
|
39
|
+
|
40
|
+
To make sure the name is formatted correctly we ask you to use use quotes
|
41
|
+
around the name, e.g.: "#{args.drop(1).join(" ")}"
|
42
|
+
|
43
|
+
No changes were made to the wallet
|
44
|
+
}
|
45
|
+
)
|
46
|
+
raise CLI::CommandError.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def assert_valid_name(name)
|
50
|
+
if name.bytesize > 252
|
51
|
+
@stream_bundle.puts_error(%'The address name "#{name}" is too long\n')
|
52
|
+
@stream_bundle.puts_error("Currently we do not allow address names over 252 bytes long\n")
|
53
|
+
@stream_bundle.puts_error("The address was not added to the wallet\n")
|
54
|
+
raise CLI::CommandError.new
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Commands
|
3
|
+
module SatoshiWallet
|
4
|
+
class ShowAddressesCommand
|
5
|
+
def initialize(stream_bundle)
|
6
|
+
@stream_bundle = stream_bundle
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(wallet, options)
|
10
|
+
@stream_bundle.puts_output("# All addresses in #{options[:wallet_filename]}")
|
11
|
+
@stream_bundle.puts_output(wallet.addresses)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Commands
|
3
|
+
module SatoshiWallet
|
4
|
+
class ShowVersionCommand
|
5
|
+
def initialize(stream_bundle)
|
6
|
+
@stream_bundle = stream_bundle
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(wallet, options = { })
|
10
|
+
@stream_bundle.puts_output(wallet.version)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
|
3
|
+
require 'bitcoin/cli' # circular dependency on CLI for CommandError
|
4
|
+
require 'bitcoin/commands/satoshi_wallet'
|
5
|
+
require 'bitcoin/data_access/satoshi/satoshi_wallet'
|
6
|
+
require 'bitcoin/domain/bitcoin_address'
|
7
|
+
require 'bitcoin/filesystem/empty_temp_dir'
|
8
|
+
|
9
|
+
module Bitcoin
|
10
|
+
module Commands
|
11
|
+
class SatoshiWalletCommand
|
12
|
+
TEMP_DB_PATH = "bitcoinrb_db_tmp"
|
13
|
+
|
14
|
+
def initialize(stream_bundle, command_environment)
|
15
|
+
@stream_bundle = stream_bundle
|
16
|
+
@command_environment = command_environment
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(args)
|
20
|
+
command_name, *remaining_args = *args
|
21
|
+
command_class =
|
22
|
+
case command_name
|
23
|
+
when "add-address"
|
24
|
+
SatoshiWallet::AddAddressCommand
|
25
|
+
when "show-addresses"
|
26
|
+
SatoshiWallet::ShowAddressesCommand
|
27
|
+
when "show-version"
|
28
|
+
SatoshiWallet::ShowVersionCommand
|
29
|
+
else
|
30
|
+
@stream_bundle.puts_error(%'Unknown satoshi-wallet subcommand: "#{command_name}"')
|
31
|
+
raise CLI::CommandError.new
|
32
|
+
end
|
33
|
+
@command_environment.run_command(command_class.new(@stream_bundle), remaining_args)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'bitcoin/filesystem/empty_temp_dir'
|
2
|
+
|
3
|
+
module Bitcoin
|
4
|
+
module Commands
|
5
|
+
class SatoshiWalletCommandEnvironment
|
6
|
+
def initialize(stream_bundle)
|
7
|
+
@stream_bundle = stream_bundle
|
8
|
+
end
|
9
|
+
|
10
|
+
def run_command(command, arguments)
|
11
|
+
wallet_filename, *remaining_args = *arguments
|
12
|
+
|
13
|
+
if !File.exist?(wallet_filename)
|
14
|
+
@stream_bundle.puts_error("Couldn't find wallet file: #{wallet_filename}")
|
15
|
+
raise CLI::CommandError.new
|
16
|
+
end
|
17
|
+
|
18
|
+
absolute_wallet_path = File.expand_path(wallet_filename)
|
19
|
+
|
20
|
+
FileSystem::EmptyTempDir.open(with_name: "bitcoinrb_db_tmp", parallel_to: absolute_wallet_path) do |temp_dir|
|
21
|
+
DataAccess::Satoshi::SatoshiWallet.open(wallet_filename: absolute_wallet_path, db_dirname: temp_dir.absolute_path) do |wallet|
|
22
|
+
command.run(wallet, wallet_filename: wallet_filename, args: remaining_args)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'stream_bundle'
|
2
|
+
|
3
|
+
module Bitcoin
|
4
|
+
module Console
|
5
|
+
# CapturingStreamBundles are StreamBundles that record the data written to them
|
6
|
+
# for easy inspection later. They are useful for testing purposes.
|
7
|
+
class CapturingStreamBundle
|
8
|
+
class << self
|
9
|
+
def test_bundle
|
10
|
+
new(StringIO.new, StringIO.new, StringIO.new)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(input, output, error)
|
15
|
+
@stream_bundle = StreamBundle.new(input, output, error)
|
16
|
+
|
17
|
+
@captured_output = StringIO.new
|
18
|
+
@captured_error = StringIO.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def puts_output(stringlike)
|
22
|
+
@stream_bundle.puts_output(stringlike)
|
23
|
+
@captured_output.puts(stringlike)
|
24
|
+
end
|
25
|
+
|
26
|
+
def puts_error(stringlike)
|
27
|
+
@stream_bundle.puts_error(stringlike)
|
28
|
+
@captured_error.puts(stringlike)
|
29
|
+
end
|
30
|
+
|
31
|
+
def captured_output
|
32
|
+
@captured_output.rewind
|
33
|
+
@captured_output.read
|
34
|
+
end
|
35
|
+
|
36
|
+
def captured_error
|
37
|
+
@captured_error.rewind
|
38
|
+
@captured_error.read
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|