correios-frete-alternative 1.10.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +28 -0
- data/.rspec +2 -0
- data/.travis.yml +18 -0
- data/CHANGELOG.rdoc +107 -0
- data/Gemfile +2 -0
- data/README.rdoc +364 -0
- data/Rakefile +25 -0
- data/bin/console +12 -0
- data/correios-frete-alternative.gemspec +31 -0
- data/lib/correios-frete-alternative.rb +9 -0
- data/lib/correios/frete.rb +19 -0
- data/lib/correios/frete/calculador.rb +69 -0
- data/lib/correios/frete/pacote.rb +69 -0
- data/lib/correios/frete/pacote_item.rb +27 -0
- data/lib/correios/frete/parser.rb +20 -0
- data/lib/correios/frete/servico.rb +97 -0
- data/lib/correios/frete/version.rb +6 -0
- data/lib/correios/frete/web_service.rb +87 -0
- data/misc/correios_frete_manual_v1.7.pdf +0 -0
- data/misc/correios_frete_manual_v1.9.pdf +0 -0
- data/spec/correios/frete/calculador_spec.rb +174 -0
- data/spec/correios/frete/pacote_item_spec.rb +45 -0
- data/spec/correios/frete/pacote_spec.rb +188 -0
- data/spec/correios/frete/parser_spec.rb +44 -0
- data/spec/correios/frete/servico_spec.rb +185 -0
- data/spec/correios/frete/web_service_spec.rb +18 -0
- data/spec/correios/frete_spec.rb +79 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/mock_request.rb +17 -0
- data/spec/support/responses/success-response-many-services.xml +27 -0
- data/spec/support/responses/success-response-one-service.xml +15 -0
- metadata +187 -0
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
6
|
+
spec.pattern = FileList["spec/**/*_spec.rb"]
|
7
|
+
end
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
10
|
+
spec.pattern = "spec/**/*_spec.rb"
|
11
|
+
spec.rcov = true
|
12
|
+
end
|
13
|
+
|
14
|
+
task :default => :spec
|
15
|
+
|
16
|
+
require 'rdoc/task'
|
17
|
+
Rake::RDocTask.new do |rdoc|
|
18
|
+
version = File.exist?("VERSION") ? File.read("VERSION") : ""
|
19
|
+
|
20
|
+
rdoc.rdoc_dir = "rdoc"
|
21
|
+
rdoc.title = "correios-frete #{version}"
|
22
|
+
rdoc.rdoc_files.include("README*")
|
23
|
+
rdoc.rdoc_files.include("CHANGELOG*")
|
24
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
25
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'correios/frete/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "correios-frete-alternative"
|
7
|
+
gem.version = Correios::Frete::VERSION
|
8
|
+
gem.authors = ["Fernando Hamasaki de Amorim"]
|
9
|
+
gem.email = ["prodis@gmail.com"]
|
10
|
+
gem.summary = "Calculo de frete dos Correios."
|
11
|
+
gem.description = "Calculo de frete utilizando o Web Service dos Correios (http://www.correios.com.br/webservices).\nOs servicos de frete suportados sao PAC, SEDEX, SEDEX a Cobrar, SEDEX 10, SEDEX Hoje e e-SEDEX."
|
12
|
+
gem.homepage = "http://prodis.blog.br/correios-frete-gem-para-calculo-de-frete-dos-correios"
|
13
|
+
gem.licenses = ["MIT"]
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($\)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.platform = Gem::Platform::RUBY
|
21
|
+
gem.required_ruby_version = Gem::Requirement.new(">= 1.9.3")
|
22
|
+
|
23
|
+
gem.add_dependency "log-me", "~> 0.0.10"
|
24
|
+
gem.add_dependency "nokogiri"
|
25
|
+
gem.add_dependency "sax-machine", "~> 1.3"
|
26
|
+
|
27
|
+
gem.add_development_dependency "rake", "~> 12.0"
|
28
|
+
gem.add_development_dependency "pry", "~> 0.10"
|
29
|
+
gem.add_development_dependency "rspec", "~> 3.5"
|
30
|
+
gem.add_development_dependency "webmock", "~> 3.0"
|
31
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'correios/frete'
|
4
|
+
require 'correios/frete/calculador'
|
5
|
+
require 'correios/frete/pacote'
|
6
|
+
require 'correios/frete/pacote_item'
|
7
|
+
require 'correios/frete/parser'
|
8
|
+
require 'correios/frete/servico'
|
9
|
+
require 'correios/frete/web_service'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'log-me'
|
3
|
+
|
4
|
+
module Correios
|
5
|
+
module Frete
|
6
|
+
extend LogMe
|
7
|
+
|
8
|
+
module Timeout
|
9
|
+
DEFAULT_REQUEST_TIMEOUT = 10 #seconds
|
10
|
+
attr_writer :request_timeout
|
11
|
+
|
12
|
+
def request_timeout
|
13
|
+
(@request_timeout ||= DEFAULT_REQUEST_TIMEOUT).to_i
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
extend Timeout
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module Correios
|
3
|
+
module Frete
|
4
|
+
class Calculador
|
5
|
+
attr_accessor :cep_origem, :cep_destino
|
6
|
+
attr_accessor :diametro, :mao_propria, :aviso_recebimento, :valor_declarado
|
7
|
+
attr_accessor :codigo_empresa, :senha, :encomenda
|
8
|
+
attr_writer :peso, :comprimento, :largura, :altura, :formato
|
9
|
+
|
10
|
+
DEFAULT_OPTIONS = {
|
11
|
+
:peso => 0.0,
|
12
|
+
:comprimento => 0.0,
|
13
|
+
:largura => 0.0,
|
14
|
+
:altura => 0.0,
|
15
|
+
:diametro => 0.0,
|
16
|
+
:formato => :caixa_pacote,
|
17
|
+
:mao_propria => false,
|
18
|
+
:aviso_recebimento => false,
|
19
|
+
:valor_declarado => 0.0
|
20
|
+
}
|
21
|
+
|
22
|
+
def initialize(options = {})
|
23
|
+
DEFAULT_OPTIONS.merge(options).each do |attr, value|
|
24
|
+
self.send("#{attr}=", value)
|
25
|
+
end
|
26
|
+
|
27
|
+
yield self if block_given?
|
28
|
+
end
|
29
|
+
|
30
|
+
[:peso, :comprimento, :largura, :altura, :formato].each do |method|
|
31
|
+
define_method method do
|
32
|
+
@encomenda ? @encomenda.send(method) : instance_variable_get("@#{method}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def calcular(*service_types)
|
37
|
+
response = web_service(service_types).request!
|
38
|
+
services = parser.servicos(response)
|
39
|
+
|
40
|
+
if service_types.size == 1
|
41
|
+
services.values.first
|
42
|
+
else
|
43
|
+
services
|
44
|
+
end
|
45
|
+
end
|
46
|
+
alias calculate calcular
|
47
|
+
|
48
|
+
def method_missing(method_name, *args)
|
49
|
+
return calcular($2.to_sym) if method_name.to_s =~ /^(calcular|calculate)_(.*)/ && Correios::Frete::Servico.code_from_type($2.to_sym)
|
50
|
+
super
|
51
|
+
end
|
52
|
+
|
53
|
+
def respond_to?(method_name)
|
54
|
+
return true if method_name.to_s =~ /^(calcular|calculate)_(.*)/ && Correios::Frete::Servico.code_from_type($2.to_sym)
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def web_service(service_types)
|
61
|
+
Correios::Frete::WebService.new(self, service_types)
|
62
|
+
end
|
63
|
+
|
64
|
+
def parser
|
65
|
+
@parser ||= Correios::Frete::Parser.new
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module Correios
|
3
|
+
module Frete
|
4
|
+
class Pacote
|
5
|
+
attr_reader :peso, :comprimento, :altura, :largura, :volume
|
6
|
+
|
7
|
+
MIN_DIMENSIONS = {
|
8
|
+
:comprimento => 16.0,
|
9
|
+
:largura => 11.0,
|
10
|
+
:altura => 2.0
|
11
|
+
}
|
12
|
+
|
13
|
+
def initialize(itens = nil)
|
14
|
+
@peso = @comprimento = @largura = @altura = @volume = 0.0
|
15
|
+
@itens = []
|
16
|
+
|
17
|
+
itens.each { |item| adicionar_item(item) } if itens
|
18
|
+
end
|
19
|
+
|
20
|
+
def formato
|
21
|
+
:caixa_pacote
|
22
|
+
end
|
23
|
+
|
24
|
+
def itens
|
25
|
+
@itens
|
26
|
+
end
|
27
|
+
alias items itens
|
28
|
+
|
29
|
+
def adicionar_item(item)
|
30
|
+
return unless item
|
31
|
+
|
32
|
+
item = Correios::Frete::PacoteItem.new(item) if item.is_a?(Hash)
|
33
|
+
@itens << item
|
34
|
+
|
35
|
+
calcular_medidas(item)
|
36
|
+
item
|
37
|
+
end
|
38
|
+
alias add_item adicionar_item
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def calcular_medidas(item)
|
43
|
+
@peso += item.peso
|
44
|
+
@volume += item.volume
|
45
|
+
|
46
|
+
if @itens.size == 1
|
47
|
+
@comprimento = item.comprimento
|
48
|
+
@largura = item.largura
|
49
|
+
@altura = item.altura
|
50
|
+
else
|
51
|
+
dimensao = @volume.to_f**(1.0/3)
|
52
|
+
@comprimento = @largura = @altura = dimensao
|
53
|
+
end
|
54
|
+
|
55
|
+
min_dimension_values
|
56
|
+
end
|
57
|
+
|
58
|
+
def min_dimension_values()
|
59
|
+
@comprimento = min(@comprimento, MIN_DIMENSIONS[:comprimento])
|
60
|
+
@largura = min(@largura, MIN_DIMENSIONS[:largura])
|
61
|
+
@altura = min(@altura, MIN_DIMENSIONS[:altura])
|
62
|
+
end
|
63
|
+
|
64
|
+
def min(value, minimum)
|
65
|
+
(value < minimum) ? minimum : value
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module Correios
|
3
|
+
module Frete
|
4
|
+
class PacoteItem
|
5
|
+
attr_accessor :peso, :comprimento, :largura, :altura
|
6
|
+
|
7
|
+
DEFAULT_OPTIONS = {
|
8
|
+
:peso => 0.0,
|
9
|
+
:comprimento => 0.0,
|
10
|
+
:largura => 0.0,
|
11
|
+
:altura => 0.0
|
12
|
+
}
|
13
|
+
|
14
|
+
def initialize(options = {})
|
15
|
+
DEFAULT_OPTIONS.merge(options).each do |attr, value|
|
16
|
+
self.send("#{attr}=", value)
|
17
|
+
end
|
18
|
+
|
19
|
+
yield self if block_given?
|
20
|
+
end
|
21
|
+
|
22
|
+
def volume
|
23
|
+
@comprimento * @largura * @altura
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module Correios
|
5
|
+
module Frete
|
6
|
+
class Parser
|
7
|
+
def servicos(xml)
|
8
|
+
servicos = {}
|
9
|
+
xml = xml.encode("UTF-8", "ISO-8859-1")
|
10
|
+
|
11
|
+
Nokogiri::XML(xml).root.elements.each do |element|
|
12
|
+
servico = Correios::Frete::Servico.new.parse(element.to_xml)
|
13
|
+
servicos[servico.tipo] = servico
|
14
|
+
end
|
15
|
+
|
16
|
+
servicos
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'sax-machine'
|
3
|
+
|
4
|
+
module Correios
|
5
|
+
module Frete
|
6
|
+
class Servico
|
7
|
+
include SAXMachine
|
8
|
+
|
9
|
+
AVAILABLE_SERVICES = {
|
10
|
+
"04510" => { :type => :pac, :name => "PAC", :description => "PAC sem contrato" },
|
11
|
+
"41068" => { :type => :pac_com_contrato, :name => "PAC", :description => "PAC com contrato" },
|
12
|
+
"41300" => { :type => :pac_gf, :name => "PAC GF", :description => "PAC para grandes formatos" },
|
13
|
+
"04014" => { :type => :sedex, :name => "SEDEX", :description => "SEDEX sem contrato" },
|
14
|
+
"40045" => { :type => :sedex_a_cobrar, :name => "SEDEX a Cobrar", :description => "SEDEX a Cobrar, sem contrato" },
|
15
|
+
"40126" => { :type => :sedex_a_cobrar_com_contrato, :name => "SEDEX a Cobrar", :description => "SEDEX a Cobrar, com contrato" },
|
16
|
+
"40215" => { :type => :sedex_10, :name => "SEDEX 10", :description => "SEDEX 10, sem contrato" },
|
17
|
+
"40290" => { :type => :sedex_hoje, :name => "SEDEX Hoje", :description => "SEDEX Hoje, sem contrato" },
|
18
|
+
"40096" => { :type => :sedex_com_contrato_1, :name => "SEDEX", :description => "SEDEX com contrato" },
|
19
|
+
"40436" => { :type => :sedex_com_contrato_2, :name => "SEDEX", :description => "SEDEX com contrato" },
|
20
|
+
"40444" => { :type => :sedex_com_contrato_3, :name => "SEDEX", :description => "SEDEX com contrato" },
|
21
|
+
"40568" => { :type => :sedex_com_contrato_4, :name => "SEDEX", :description => "SEDEX com contrato" },
|
22
|
+
"40606" => { :type => :sedex_com_contrato_5, :name => "SEDEX", :description => "SEDEX com contrato" },
|
23
|
+
"81019" => { :type => :e_sedex, :name => "e-SEDEX", :description => "e-SEDEX, com contrato" },
|
24
|
+
"81027" => { :type => :e_sedex_prioritario, :name => "e-SEDEX", :description => "e-SEDEX Prioritário, com contrato" },
|
25
|
+
"81035" => { :type => :e_sedex_express, :name => "e-SEDEX", :description => "e-SEDEX Express, com contrato" },
|
26
|
+
"81868" => { :type => :e_sedex_grupo_1, :name => "e-SEDEX", :description => "(Grupo 1) e-SEDEX, com contrato" },
|
27
|
+
"81833" => { :type => :e_sedex_grupo_2, :name => "e-SEDEX", :description => "(Grupo 2) e-SEDEX, com contrato" },
|
28
|
+
"81850" => { :type => :e_sedex_grupo_3, :name => "e-SEDEX", :description => "(Grupo 3) e-SEDEX, com contrato" }
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
element :Codigo, :as => :codigo
|
32
|
+
element :Valor, :as => :valor
|
33
|
+
element :PrazoEntrega, :as => :prazo_entrega
|
34
|
+
element :ValorMaoPropria, :as => :valor_mao_propria
|
35
|
+
element :ValorAvisoRecebimento, :as => :valor_aviso_recebimento
|
36
|
+
element :ValorValorDeclarado, :as => :valor_valor_declarado
|
37
|
+
element :EntregaDomiciliar, :as => :entrega_domiciliar
|
38
|
+
element :EntregaSabado, :as => :entrega_sabado
|
39
|
+
element :Erro, :as => :erro
|
40
|
+
element :MsgErro, :as => :msg_erro
|
41
|
+
attr_reader :tipo, :nome, :descricao
|
42
|
+
|
43
|
+
alias_method :original_parse, :parse
|
44
|
+
|
45
|
+
def parse(xml_text)
|
46
|
+
original_parse xml_text
|
47
|
+
|
48
|
+
if AVAILABLE_SERVICES[codigo]
|
49
|
+
@tipo = AVAILABLE_SERVICES[codigo][:type]
|
50
|
+
@nome = AVAILABLE_SERVICES[codigo][:name]
|
51
|
+
@descricao = AVAILABLE_SERVICES[codigo][:description]
|
52
|
+
end
|
53
|
+
|
54
|
+
cast_to_float! :valor, :valor_mao_propria, :valor_aviso_recebimento, :valor_valor_declarado
|
55
|
+
cast_to_int! :prazo_entrega
|
56
|
+
cast_to_boolean! :entrega_domiciliar, :entrega_sabado
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def success?
|
61
|
+
valor > 0.0
|
62
|
+
end
|
63
|
+
alias sucesso? success?
|
64
|
+
|
65
|
+
def error?
|
66
|
+
!success?
|
67
|
+
end
|
68
|
+
alias erro? error?
|
69
|
+
|
70
|
+
def self.code_from_type(type)
|
71
|
+
# I don't use select method for Ruby 1.8.7 compatibility.
|
72
|
+
AVAILABLE_SERVICES.map { |key, value| key if value[:type] == type }.compact.first
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def cast_to_float!(*attributes)
|
78
|
+
attributes.each do |attr|
|
79
|
+
value = send(attr).to_s.gsub(".", "").gsub("," ,".")
|
80
|
+
instance_variable_set("@#{attr}", value.to_f)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def cast_to_int!(*attributes)
|
85
|
+
attributes.each do |attr|
|
86
|
+
instance_variable_set("@#{attr}", send(attr).to_i)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def cast_to_boolean!(*attributes)
|
91
|
+
attributes.each do |attr|
|
92
|
+
instance_variable_set("@#{attr}", send(attr) == "S")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'net/http'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Correios
|
6
|
+
module Frete
|
7
|
+
class WebService
|
8
|
+
URL = "http://ws.correios.com.br/calculador/CalcPrecoPrazo.aspx"
|
9
|
+
FORMATS = { :caixa_pacote => 1, :rolo_prisma => 2, :envelope => 3 }
|
10
|
+
CONDITIONS = { true => "S", false => "N" }
|
11
|
+
|
12
|
+
def initialize(frete, service_types)
|
13
|
+
@url = "#{URL}?#{params_for(frete, service_types)}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def request!
|
17
|
+
response = with_log { http_request(@url) }
|
18
|
+
response.body
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def http_request(url)
|
24
|
+
uri = URI.parse(url)
|
25
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
26
|
+
|
27
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
28
|
+
http.open_timeout = Correios::Frete.request_timeout
|
29
|
+
http.request(request)
|
30
|
+
end
|
31
|
+
|
32
|
+
def params_for(frete, service_types)
|
33
|
+
"sCepOrigem=#{frete.cep_origem}&" +
|
34
|
+
"sCepDestino=#{frete.cep_destino}&" +
|
35
|
+
"nVlPeso=#{frete.peso}&" +
|
36
|
+
"nVlComprimento=#{format_decimal(frete.comprimento)}&" +
|
37
|
+
"nVlLargura=#{format_decimal(frete.largura)}&" +
|
38
|
+
"nVlAltura=#{format_decimal(frete.altura)}&" +
|
39
|
+
"nVlDiametro=#{format_decimal(frete.diametro)}&" +
|
40
|
+
"nCdFormato=#{FORMATS[frete.formato]}&" +
|
41
|
+
"sCdMaoPropria=#{CONDITIONS[frete.mao_propria]}&" +
|
42
|
+
"sCdAvisoRecebimento=#{CONDITIONS[frete.aviso_recebimento]}&" +
|
43
|
+
"nVlValorDeclarado=#{format_decimal(format("%.2f" % frete.valor_declarado))}&" +
|
44
|
+
"nCdServico=#{service_codes_for(service_types)}&" +
|
45
|
+
"nCdEmpresa=#{frete.codigo_empresa}&" +
|
46
|
+
"sDsSenha=#{frete.senha}&" +
|
47
|
+
"StrRetorno=xml"
|
48
|
+
end
|
49
|
+
|
50
|
+
def format_decimal(value)
|
51
|
+
value.to_s.gsub(".", ",")
|
52
|
+
end
|
53
|
+
|
54
|
+
def service_codes_for(service_types)
|
55
|
+
service_types.map { |type| Correios::Frete::Servico.code_from_type(type) }.join(",")
|
56
|
+
end
|
57
|
+
|
58
|
+
def with_log
|
59
|
+
Correios::Frete.log format_request_message
|
60
|
+
response = yield
|
61
|
+
Correios::Frete.log format_response_message(response)
|
62
|
+
response
|
63
|
+
end
|
64
|
+
|
65
|
+
def format_request_message
|
66
|
+
message = with_line_break { "Correios-Frete Request:" }
|
67
|
+
message << with_line_break { "GET #{@url}" }
|
68
|
+
end
|
69
|
+
|
70
|
+
def format_response_message(response)
|
71
|
+
message = with_line_break { "Correios-Frete Response:" }
|
72
|
+
message << with_line_break { "HTTP/#{response.http_version} #{response.code} #{response.message}" }
|
73
|
+
message << with_line_break { format_headers_for(response) } if Correios::Frete.log_level == :debug
|
74
|
+
message << with_line_break { response.body }
|
75
|
+
end
|
76
|
+
|
77
|
+
def format_headers_for(http)
|
78
|
+
# I'm using an empty block in each_header method for Ruby 1.8.7 compatibility.
|
79
|
+
http.each_header{}.map { |name, values| "#{name}: #{values.first}" }.join("\n")
|
80
|
+
end
|
81
|
+
|
82
|
+
def with_line_break
|
83
|
+
"#{yield}\n"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|