chatwork 0.5.0 → 0.6.0

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.
Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. data/.env.example +4 -0
  3. data/.gitignore +1 -0
  4. data/.gitmodules +3 -0
  5. data/.rubocop.yml +10 -0
  6. data/.travis.yml +26 -0
  7. data/.yardopts +2 -0
  8. data/CHANGELOG.md +33 -1
  9. data/Gemfile +13 -1
  10. data/README.md +15 -2
  11. data/Rakefile +2 -2
  12. data/bin/console +10 -0
  13. data/chatwork.gemspec +20 -9
  14. data/lib/chatwork.rb +32 -36
  15. data/lib/chatwork/base_client.rb +8 -11
  16. data/lib/chatwork/chatwork_error.rb +6 -16
  17. data/lib/chatwork/client.rb +2 -2
  18. data/lib/chatwork/contacts.rb +24 -8
  19. data/lib/chatwork/entity_methods.rb +36 -0
  20. data/lib/chatwork/file.rb +63 -0
  21. data/lib/chatwork/incoming_request.rb +71 -0
  22. data/lib/chatwork/me.rb +33 -8
  23. data/lib/chatwork/member.rb +55 -4
  24. data/lib/chatwork/message.rb +147 -6
  25. data/lib/chatwork/my_status.rb +25 -0
  26. data/lib/chatwork/my_task.rb +36 -8
  27. data/lib/chatwork/oauth_client.rb +3 -3
  28. data/lib/chatwork/room.rb +119 -6
  29. data/lib/chatwork/task.rb +96 -4
  30. data/lib/chatwork/token.rb +2 -2
  31. data/lib/chatwork/version.rb +1 -1
  32. data/spec/lib/chatwork/chatwork_error_spec.rb +10 -8
  33. data/spec/lib/chatwork/client_spec.rb +14 -14
  34. data/spec/lib/chatwork/contacts_spec.rb +11 -0
  35. data/spec/lib/chatwork/entity_methods_spec.rb +21 -0
  36. data/spec/lib/chatwork/file_spec.rb +37 -0
  37. data/spec/lib/chatwork/incoming_request_spec.rb +35 -0
  38. data/spec/lib/chatwork/me_spec.rb +39 -0
  39. data/spec/lib/chatwork/member_spec.rb +40 -9
  40. data/spec/lib/chatwork/message_spec.rb +102 -0
  41. data/spec/lib/chatwork/my_status_spec.rb +13 -0
  42. data/spec/lib/chatwork/my_task_spec.rb +19 -0
  43. data/spec/lib/chatwork/oauth_client_spec.rb +7 -7
  44. data/spec/lib/chatwork/room_spec.rb +102 -0
  45. data/spec/lib/chatwork/task_spec.rb +77 -0
  46. data/spec/lib/chatwork/token_spec.rb +26 -7
  47. data/spec/lib/chatwork_spec.rb +26 -19
  48. data/spec/lib/support/utils/raml_parser_spec.rb +96 -0
  49. data/spec/spec_helper.rb +32 -6
  50. data/spec/support/contexts/api_context.rb +43 -0
  51. data/spec/support/examples/a_chatwork_api.rb +12 -0
  52. data/spec/support/matchers/match_example.rb +16 -0
  53. data/spec/support/utils/raml_parser.rb +86 -0
  54. metadata +214 -10
  55. data/lib/chatwork/entity.rb +0 -29
  56. data/lib/chatwork/operations.rb +0 -48
  57. data/spec/shared_oauth_stubs.rb +0 -49
  58. data/spec/shared_stubs.rb +0 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: be16993941272b82492aa6671c2cce25e9ea8a4d
4
- data.tar.gz: 63c1282e35926da56a30e5a1cca103633414fd02
2
+ SHA256:
3
+ metadata.gz: 5c77e7a8ed9f1542d07d0a5358aee0f0a47df235629691a33824d48b743fb1c2
4
+ data.tar.gz: d1ab0c06c73e6fc2adb820bedfbe81b7fdda3935d601d2084067dcaee3562aa8
5
5
  SHA512:
6
- metadata.gz: 33cdf63bf2505a9a02da3958f86ed11851e01838a75da820cb6b67ba3db401686aeb746a05d68f25b1be7a33b8274d4b6b555677b2f66772cbbf84e2b5e96c37
7
- data.tar.gz: dce5ad9a60dcb6f843761055fd122a8414472d97f343198466e3b63c0238514a5455126e4bdb6d75af95fa02d12c3629c7aacec41fb94fa999193800985a81af
6
+ metadata.gz: c1e4a88d1c57069063ed23917bb9df6336f7ee8493c166083ebeb9feceb2e35c59af9e01d19c3b887506d869d4fa0a4c69b0d61f4be6e5b4b96dbedb0e51d2c0
7
+ data.tar.gz: d12ffa27c12910c660d14f6c72b275d3a1a8fa656eac0c2c6a388ceb5d708424269a75ecee9d190c0009896f843e01c01e195e0e593a5d77e49ea1c47231f193
@@ -0,0 +1,4 @@
1
+ CHATWORK_API_TOKEN=
2
+ CHATWORK_ACCESS_TOKEN=
3
+ CHATWORK_CLIENT_ID=
4
+ CHATWORK_CLIENT_SECRET=
data/.gitignore CHANGED
@@ -17,3 +17,4 @@ test/version_tmp
17
17
  tmp
18
18
  .ruby-gemset
19
19
  .ruby-version
20
+ .env
@@ -0,0 +1,3 @@
1
+ [submodule "api"]
2
+ path = api
3
+ url = https://github.com/chatwork/api.git
@@ -0,0 +1,10 @@
1
+ inherit_gem:
2
+ onkcop:
3
+ - "config/rubocop.yml"
4
+ - "config/rspec.yml"
5
+
6
+ require: rubocop-rspec
7
+
8
+ # module name is `ChatWork`, but I want to use `chatwork` as filename
9
+ RSpec/FilePath:
10
+ Enabled: false
@@ -0,0 +1,26 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.1
5
+ - 2.2
6
+ - 2.3
7
+ - 2.4.1
8
+ - 2.5.0
9
+ - ruby-head
10
+ bundler_args: "--jobs=2"
11
+ cache: bundler
12
+ before_install:
13
+ - gem update --system --no-document
14
+ - gem install bundler -v 1.16.1 --no-document
15
+ script:
16
+ - bundle exec rake spec
17
+ - bundle exec rubocop
18
+ branches:
19
+ only:
20
+ - master
21
+ matrix:
22
+ allow_failures:
23
+ - rvm: ruby-head
24
+ env:
25
+ global:
26
+ secure: Ntdheemdu1GavPR/3kPfQg1tR13FTP0jE9KxsSKcG32VbIzM69l22OXXwYttFexMhA1dMNytv5bKGIszeiO+YjwzDXkcDHF7ZULc24epsGCOVfNax4g47Q+Lgt2kpAsx8V/8/SIpK7VeBhc2nPvhAKPjTZ8ddN2gLHk4wifLRFA=
@@ -0,0 +1,2 @@
1
+ --markup markdown
2
+ --no-private
@@ -1,4 +1,36 @@
1
1
  # Change Log
2
+ ## Unreleased
3
+ [Full Changelog](https://github.com/asonas/chatwork-ruby/compare/v0.6.0...master)
4
+
5
+ ## v0.6.0
6
+ [Full Changelog](https://github.com/asonas/chatwork-ruby/compare/v0.5.0...v0.6.0)
7
+
8
+ * Support all ChatWork APIs
9
+ * https://github.com/asonas/chatwork-ruby/pull/34
10
+ * raise error when status 4xx and 5xx
11
+ * https://github.com/asonas/chatwork-ruby/pull/35
12
+ * Returns `Hashie::Mash` instead of `Hash`
13
+ * https://github.com/asonas/chatwork-ruby/pull/36
14
+ * and many refactorings!
15
+ * https://github.com/asonas/chatwork-ruby/pull/22
16
+ * https://github.com/asonas/chatwork-ruby/pull/23
17
+ * https://github.com/asonas/chatwork-ruby/pull/24
18
+ * https://github.com/asonas/chatwork-ruby/pull/25
19
+ * https://github.com/asonas/chatwork-ruby/pull/26
20
+ * https://github.com/asonas/chatwork-ruby/pull/27
21
+ * https://github.com/asonas/chatwork-ruby/pull/28
22
+ * https://github.com/asonas/chatwork-ruby/pull/29
23
+ * https://github.com/asonas/chatwork-ruby/pull/30
24
+
25
+ ## [v0.5.0](https://github.com/asonas/chatwork-ruby/tree/v0.5.0) (2017-11-29)
26
+ [Full Changelog](https://github.com/asonas/chatwork-ruby/compare/v0.4.1...v0.5.0)
27
+
28
+ **Merged pull requests:**
29
+
30
+ - Support ChatWork OAuth [\#21](https://github.com/asonas/chatwork-ruby/pull/21) ([sue445](https://github.com/sue445))
31
+ - Refactor: remove `require` in each spec file [\#20](https://github.com/asonas/chatwork-ruby/pull/20) ([sue445](https://github.com/sue445))
32
+ - Relax rspec version [\#19](https://github.com/asonas/chatwork-ruby/pull/19) ([sue445](https://github.com/sue445))
33
+ - Add block expression [\#16](https://github.com/asonas/chatwork-ruby/pull/16) ([yuyaan](https://github.com/yuyaan))
2
34
 
3
35
  ## [v0.4.1](https://github.com/asonas/chatwork-ruby/tree/v0.4.1) (2017-02-17)
4
36
  [Full Changelog](https://github.com/asonas/chatwork-ruby/compare/v0.4.0...v0.4.1)
@@ -95,4 +127,4 @@
95
127
  ## [v0.0.1](https://github.com/asonas/chatwork-ruby/tree/v0.0.1) (2013-11-28)
96
128
 
97
129
 
98
- \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
130
+ \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
data/Gemfile CHANGED
@@ -1,4 +1,16 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in chatwork.gemspec
4
4
  gemspec
5
+
6
+ if Gem::Version.create(RUBY_VERSION) < Gem::Version.create("2.3.0")
7
+ group :test do
8
+ gem "backport_dig", group: :development
9
+ end
10
+ end
11
+
12
+ if Gem::Version.create(RUBY_VERSION) < Gem::Version.create("2.2.2")
13
+ group :test do
14
+ gem "activesupport", "< 5.0.0", group: :development
15
+ end
16
+ end
data/README.md CHANGED
@@ -1,6 +1,11 @@
1
- # Chatwork
1
+ # ChatWork
2
2
 
3
- TODO: Write a gem description
3
+ Ruby bindings of ChatWork API
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/chatwork.svg)](https://badge.fury.io/rb/chatwork)
6
+ [![Build Status](https://travis-ci.org/asonas/chatwork-ruby.svg?branch=master)](https://travis-ci.org/asonas/chatwork-ruby)
7
+ [![Coverage Status](https://coveralls.io/repos/github/asonas/chatwork-ruby/badge.svg?branch=master)](https://coveralls.io/github/asonas/chatwork-ruby)
8
+ [![Dependency Status](https://gemnasium.com/badges/github.com/asonas/chatwork-ruby.svg)](https://gemnasium.com/github.com/asonas/chatwork-ruby)
4
9
 
5
10
  ## Installation
6
11
 
@@ -87,6 +92,14 @@ ChatWork::Message.create(room_id: 1234, body: "Hello, ChatWork!")
87
92
  $ CHATWORK_CLIENT_ID=xxx CHATWORK_CLIENT_SECRET=xxx REFRESH_TOKEN=xxx ruby refresh_access_token.rb
88
93
  ```
89
94
 
95
+ ## Development
96
+ ```bash
97
+ cp .env.example .env
98
+ vi .env
99
+
100
+ ./bin/console
101
+ ```
102
+
90
103
  ## Contributing
91
104
 
92
105
  1. Fork it
data/Rakefile CHANGED
@@ -1,9 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
2
 
3
3
  begin
4
- require 'rspec/core/rake_task'
4
+ require "rspec/core/rake_task"
5
5
  RSpec::Core::RakeTask.new
6
6
  task :default => :spec
7
7
  rescue
8
- $stderr.puts('Install rspec')
8
+ warn("Install rspec")
9
9
  end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "chatwork"
5
+ require "dotenv"
6
+
7
+ Dotenv.load
8
+
9
+ require "pry"
10
+ Pry.start
@@ -1,27 +1,38 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path("../lib", __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'chatwork/version'
3
+ require "chatwork/version"
5
4
 
6
5
  Gem::Specification.new do |spec|
7
6
  spec.name = "chatwork"
8
7
  spec.version = ChatWork::VERSION
9
- spec.authors = ["asonas"]
10
- spec.email = ["hzw1258@gmail.com"]
11
- spec.description = %q{ChatWork is cloud-based business chat tool}
12
- spec.summary = %q{Ruby bindings of Chatwork API}
8
+ spec.authors = ["asonas", "sue445"]
9
+ spec.email = ["hzw1258@gmail.com", "sue445@sue445.net"]
10
+ spec.description = "ChatWork is cloud-based business chat tool"
11
+ spec.summary = "Ruby bindings of ChatWork API"
13
12
  spec.homepage = "https://github.com/asonas/chatwork-ruby"
14
13
  spec.license = "MIT"
15
14
 
16
- spec.files = `git ls-files`.split($/)
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
15
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
16
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
18
  spec.require_paths = ["lib"]
20
19
 
21
20
  spec.add_dependency "faraday", "~> 0.9"
21
+ spec.add_dependency "faraday_middleware"
22
+ spec.add_dependency "hashie"
22
23
 
24
+ spec.add_development_dependency "activesupport"
23
25
  spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "coveralls"
27
+ spec.add_development_dependency "dotenv"
28
+ spec.add_development_dependency "onkcop", "0.52.1.0"
29
+ spec.add_development_dependency "pry-byebug"
24
30
  spec.add_development_dependency "rake"
25
31
  spec.add_development_dependency "rspec"
26
32
  spec.add_development_dependency "rspec-its"
33
+ spec.add_development_dependency "rspec-parameterized"
34
+ spec.add_development_dependency "rubocop", "0.52.1"
35
+ spec.add_development_dependency "rubocop-rspec", "1.21.0"
36
+ spec.add_development_dependency "webmock"
37
+ spec.add_development_dependency "yard"
27
38
  end
@@ -1,32 +1,40 @@
1
1
  require "chatwork/version"
2
2
 
3
3
  module ChatWork
4
- autoload(:BaseClient, 'chatwork/base_client')
5
- autoload(:Client, 'chatwork/client')
6
- autoload(:OAuthClient, 'chatwork/oauth_client')
7
- autoload(:Token, 'chatwork/token')
8
- autoload(:Operations, 'chatwork/operations')
9
- autoload(:ChatWorkError, 'chatwork/chatwork_error')
10
- autoload(:APIConnectionError, 'chatwork/chatwork_error')
11
- autoload(:APIError, 'chatwork/chatwork_error')
12
- autoload(:Room, 'chatwork/room')
13
- autoload(:Entity, 'chatwork/entity')
14
- autoload(:Message, 'chatwork/message')
15
- autoload(:Me, 'chatwork/me')
16
- autoload(:MyTask, 'chatwork/my_task')
17
- autoload(:Task, 'chatwork/task')
18
- autoload(:Member, 'chatwork/member')
19
- autoload(:Contacts, 'chatwork/contacts')
20
-
21
- @api_base = 'https://api.chatwork.com/'
22
- @oauth_api_base = 'https://oauth.chatwork.com/'
23
- @api_version = '/v2'
4
+ autoload :BaseClient, "chatwork/base_client"
5
+ autoload :APIConnectionError, "chatwork/chatwork_error"
6
+ autoload :APIError, "chatwork/chatwork_error"
7
+ autoload :ChatWorkError, "chatwork/chatwork_error"
8
+ autoload :Client, "chatwork/client"
9
+ autoload :Contacts, "chatwork/contacts"
10
+ autoload :EntityMethods, "chatwork/entity_methods"
11
+ autoload :File, "chatwork/file"
12
+ autoload :IncomingRequest, "chatwork/incoming_request"
13
+ autoload :Me, "chatwork/me"
14
+ autoload :Member, "chatwork/member"
15
+ autoload :Message, "chatwork/message"
16
+ autoload :MyStatus, "chatwork/my_status"
17
+ autoload :MyTask, "chatwork/my_task"
18
+ autoload :OAuthClient, "chatwork/oauth_client"
19
+ autoload :Room, "chatwork/room"
20
+ autoload :Task, "chatwork/task"
21
+ autoload :Token, "chatwork/token"
22
+
23
+ @api_base = "https://api.chatwork.com/"
24
+ @oauth_api_base = "https://oauth.chatwork.com/"
25
+ @api_version = "/v2"
24
26
  @api_key = nil
25
27
  @access_token = nil
26
28
  @client_id = nil
27
29
  @client_secret = nil
28
30
 
29
31
  class << self
32
+ attr_reader :api_base
33
+
34
+ attr_reader :oauth_api_base
35
+
36
+ attr_reader :api_version
37
+
30
38
  def client
31
39
  @client ||= Client.new(api_key, access_token, api_base, api_version)
32
40
  end
@@ -67,32 +75,20 @@ module ChatWork
67
75
  @oauth_client = nil
68
76
  end
69
77
 
70
- def api_base
71
- @api_base
72
- end
73
-
74
- def oauth_api_base
75
- @oauth_api_base
76
- end
77
-
78
78
  def api_key
79
- @api_key || ENV['CHATWORK_API_TOKEN']
79
+ @api_key || ENV["CHATWORK_API_TOKEN"]
80
80
  end
81
81
 
82
82
  def access_token
83
- @access_token || ENV['CHATWORK_ACCESS_TOKEN']
83
+ @access_token || ENV["CHATWORK_ACCESS_TOKEN"]
84
84
  end
85
85
 
86
86
  def client_id
87
- @client_id || ENV['CHATWORK_CLIENT_ID']
87
+ @client_id || ENV["CHATWORK_CLIENT_ID"]
88
88
  end
89
89
 
90
90
  def client_secret
91
- @client_secret || ENV['CHATWORK_CLIENT_SECRET']
92
- end
93
-
94
- def api_version
95
- @api_version
91
+ @client_secret || ENV["CHATWORK_CLIENT_SECRET"]
96
92
  end
97
93
  end
98
94
  end
@@ -1,17 +1,20 @@
1
- require 'faraday'
2
- require 'json'
1
+ require "faraday"
2
+ require "faraday_middleware"
3
+ require "hashie"
3
4
 
4
5
  module ChatWork
5
6
  class BaseClient
6
7
  def initialize(api_base, api_version, header)
7
8
  default_header = {
8
- 'User-Agent' => "ChatWork#{api_version} RubyBinding/#{ChatWork::VERSION}"
9
+ "User-Agent" => "ChatWork#{api_version} RubyBinding/#{ChatWork::VERSION}",
9
10
  }
10
11
 
11
12
  default_header.merge!(header)
12
13
 
13
14
  @conn = Faraday.new("#{api_base}#{api_version}", headers: default_header) do |builder|
14
15
  builder.request :url_encoded
16
+ builder.response :mashify
17
+ builder.response :json
15
18
  builder.adapter Faraday.default_adapter
16
19
  end
17
20
  @api_version = api_version
@@ -19,16 +22,10 @@ module ChatWork
19
22
 
20
23
  def handle_response(response)
21
24
  case response.status
22
- when 204
23
- ChatWork::ChatWorkError.from_response(response.status, response.body, response.headers)
24
25
  when 200..299
25
- begin
26
- JSON.parse(response.body)
27
- rescue JSON::ParserError => e
28
- raise ChatWork::APIConnectionError.new("Response JSON is broken. #{e.message}: #{response.body}", e)
29
- end
26
+ response.body
30
27
  else
31
- ChatWork::ChatWorkError.from_response(response.status, response.body, response.headers)
28
+ raise ChatWork::ChatWorkError.from_response(response.status, response.body, response.headers)
32
29
  end
33
30
  end
34
31
 
@@ -1,33 +1,23 @@
1
- require 'json'
2
1
  module ChatWork
3
2
  class ChatWorkError < StandardError
4
-
5
3
  def self.from_response(status, body, headers)
6
- # HTTP status 204 don't have body.
7
- return APIError.new(status, "") if status == 204
8
-
9
- hash =
10
- begin
11
- JSON.load(body)
12
- rescue JSON::ParserError => e
13
- return ChatWork::APIConnectionError.new("Response JSON is broken. #{e.message}: #{body}", e)
14
- end
15
- unless hash['errors']
4
+ unless body["errors"]
16
5
  return APIConnectionError.new("Invalid response #{body}")
17
6
  end
18
7
 
19
- if headers.has_key?('WWW-Authenticate')
20
- return AuthenticateError.new(headers['WWW-Authenticate'], status, hash['errors'])
8
+ if headers.has_key?("WWW-Authenticate")
9
+ return AuthenticateError.new(headers["WWW-Authenticate"], status, body["errors"])
21
10
  end
22
11
 
23
- APIError.new(status, hash['errors'])
12
+ APIError.new(status, body["errors"])
24
13
  end
25
14
 
26
15
  attr_reader :status
27
16
  attr_reader :error_response
28
17
 
29
18
  def initialize(message, status = nil, error_response = nil)
30
- @status, @error_response = status, error_response
19
+ @status = status
20
+ @error_response = error_response
31
21
  super(message)
32
22
  end
33
23
  end
@@ -2,9 +2,9 @@ module ChatWork
2
2
  class Client < BaseClient
3
3
  def initialize(api_key, access_token, api_base, api_version)
4
4
  if api_key
5
- super(api_base, api_version, {'X-ChatWorkToken' => api_key})
5
+ super(api_base, api_version, { "X-ChatWorkToken" => api_key })
6
6
  elsif access_token
7
- super(api_base, api_version, {'Authorization' => "Bearer #{access_token}"})
7
+ super(api_base, api_version, { "Authorization" => "Bearer #{access_token}" })
8
8
  else
9
9
  raise "Either api_key or access_token is required"
10
10
  end
@@ -1,13 +1,29 @@
1
1
  module ChatWork
2
- class Contacts < Entity
3
- install_class_operations :get
2
+ module Contacts
3
+ extend EntityMethods
4
4
 
5
- def self.path
6
- "/contacts"
7
- end
8
-
9
- def path
10
- "/contacts"
5
+ # Get the list of your contacts
6
+ #
7
+ # @see http://developer.chatwork.com/ja/endpoint_contacts.html#GET-contacts
8
+ # @see http://download.chatwork.com/ChatWork_API_Documentation.pdf
9
+ #
10
+ # @return [Array<Hashie::Mash>]
11
+ #
12
+ # @example response format
13
+ # [
14
+ # {
15
+ # "account_id": 123,
16
+ # "room_id": 322,
17
+ # "name": "John Smith",
18
+ # "chatwork_id": "tarochatworkid",
19
+ # "organization_id": 101,
20
+ # "organization_name": "Hello Company",
21
+ # "department": "Marketing",
22
+ # "avatar_image_url": "https://example.com/abc.png"
23
+ # }
24
+ # ]
25
+ def self.get
26
+ _get("/contacts")
11
27
  end
12
28
  end
13
29
  end