pactrac 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2012, Michael Alexander
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ The views and conclusions contained in the software and documentation are those
25
+ of the authors and should not be interpreted as representing official policies,
26
+ either expressed or implied, of Miniand.
@@ -0,0 +1,148 @@
1
+ PacTrac - International package tracking for Ruby
2
+ =================================================
3
+
4
+ PacTrac is a library which can be used via API and CLI to fetch raw tracking
5
+ data. In the initial alpha, only DHL is supported, but a number of other
6
+ methods are in the process of being added.
7
+
8
+ Installation
9
+ ============
10
+
11
+ ```bash
12
+ gem install pactrac
13
+ ```
14
+
15
+ CLI
16
+ ===
17
+
18
+ The binary `pactrac` is included with the gem, usable from the command line.
19
+
20
+ ### Basic usage
21
+
22
+ If not specified, the carrier company is guessed.
23
+
24
+ ```bash
25
+ $ pactrac track 1234567890
26
+ ```
27
+
28
+ ### Specifying carrier
29
+
30
+ ```bash
31
+ $ pactrac track 1234567890 --carrier Dhl
32
+ ```
33
+
34
+ ### Verifying the request
35
+
36
+ Some shipping carriers, such as EMS, require CAPTCHA style verifications. The
37
+ CLI allows for this by downloading the CAPTCHA image to a temporary directory,
38
+ and allowing you to run the command again adding your verification input.
39
+
40
+ ```bash
41
+ $ be pactrac track EE123456789CN
42
+ EMS requires verification, please view the image at
43
+ file:///tmp/pactrac_1345961387_ems.jpg and run pactrac again using the following
44
+ command:
45
+
46
+ pactrac track EE123456789CN --carrier Ems --cookie JSESSIONID\=WrTlQ59KBnfpq1pWp
47
+ hgySgYPTSln1p6rrhDd2pSvFyJt2LJGQ9dr\!-1493181672\;\ TS79e94e\=4f22237fc7dc1c7bf7
48
+ 176dda6b8992a70d84d223c37efee05039bc0060ac0ec5c32dd2e9
49
+ --verify YOUR_VERIFICATION_HERE
50
+ ```
51
+
52
+ API
53
+ ===
54
+
55
+ To include PacTrac, add `require 'pactrac'` to your script.
56
+
57
+ ### Returning error status as part of a pair
58
+
59
+ A number of functions which expect failure return a pair of values instead of
60
+ just one, to avoid returning different types and to avoid exceptions as control
61
+ flow. The first value is an error struct, with offsets `valid` and `msg`. If
62
+ `valid` is true then the function call was successful, if it is false then `msg`
63
+ will be populated with an error message.
64
+
65
+ ### Discover a carrier based on a tracking number
66
+
67
+ ```ruby
68
+ PacTrac::Carrier.for_tracking_number('EE123456789')
69
+ ```
70
+
71
+ Returns a pair of values, the first an Err struct, the second is the
72
+ corresponding carrier module.
73
+
74
+ ### Request tracking information
75
+
76
+ ```ruby
77
+ require 'pactrac'
78
+
79
+ carrier = PacTrac::Carrier.for_tracking_number('1234567890')
80
+ session = carrier.start_session # Start an HTTP session, used in requests
81
+ err, response = carrier.tracking_request('1234567890', session)
82
+ raise 'Error getting tracking information' unless err.valid
83
+ raise 'Verification needed' if response.requires_verification
84
+ err, tracking_data = carrier.parse_tracking_data(response)
85
+ raise 'Error getting response' unless err.valid
86
+ puts "Delivery via #{carrier.title} from #{tracking_data[:origin]} to
87
+ #{tracking_data[:destination]}"
88
+ # Output updates ordered by latest
89
+ tracking_data[:updates].sort_by { |u| u[:at] }.reverse.each do |u|
90
+ puts "Update time #{u[:at]}: package at #{u[:location]} with message
91
+ #{u[:message]}"
92
+ end
93
+ ```
94
+
95
+ ### Sending verification data
96
+
97
+ If you have made a tracking request and it requires verification, the
98
+ `requires_verification` value in the tracking data struct will be true, and the
99
+ `verification_image` will be set as a URI for the location of the image.
100
+ Usually the image is downloaded to the local filesystem (file://) for checking.
101
+
102
+ After manually checking the verification image, a second request is made to the
103
+ server. The cookies from the previous request need to be sent to the new
104
+ request.
105
+
106
+ ```ruby
107
+ require 'pactrac'
108
+ require 'pactrac/http/cookie'
109
+
110
+ carrier = PacTrac::Carrier.for_tracking_number('EE123456789CN')
111
+ session = carrier.start_session # Start an HTTP session, used in requests
112
+ err, response = carrier.tracking_request('EE123456789CN', session)
113
+ raise "Error getting tracking information, #{err.msg}" unless err.valid
114
+ if response.requires_verification
115
+ session.cookies = PacTrac::Http::Cookie.from_response(response)
116
+ err, response = carrier.verify('EE123456789CN', 'JHRPDS', session)
117
+ raise "Error verifying, #{err.msg}" unless err.valid
118
+ end
119
+ err, tracking_data = carrier.parse_tracking_data(response)
120
+ raise "Error getting response, #{err.msg}" unless err.valid
121
+ puts "Delivery via #{carrier.title} from #{tracking_data[:origin]} to
122
+ #{tracking_data[:destination]}"
123
+ # Output updates ordered by latest
124
+ tracking_data[:updates].sort_by { |u| u[:at] }.reverse.each do |u|
125
+ puts "Update time #{u[:at]}: package at #{u[:location]} with message
126
+ #{u[:message]}"
127
+ end
128
+ ```
129
+
130
+ Supported carriers
131
+ ==================
132
+
133
+ * EMS - PacTrac::Carrier::Ems
134
+ * DHL - PacTrac::Carrier::Dhl
135
+
136
+ Testing
137
+ =======
138
+
139
+ Automated testing is done through rspec, and can be run from rake using
140
+ `rake spec`.
141
+
142
+ Style
143
+ =====
144
+
145
+ The gem is written in a functional style, avoiding side effects of functions and
146
+ aiming to keep them pure. The gem is also written to avoid returning different
147
+ types, so many functions return a pair of values, one for exit status / message,
148
+ and the other for the actual return value.
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/ruby
2
+ require 'pactrac'
3
+ require 'pactrac/http/cookie'
4
+ require 'terminal-table'
5
+ require 'commander/import'
6
+ require 'shellwords'
7
+ require 'colorize'
8
+
9
+ program :name, 'PacTrac'
10
+ program :version, '0.0.1'
11
+ program :description, 'Internation package tracker in Ruby'
12
+
13
+ command :track do |c|
14
+ c.syntax = 'pactrac track <tracking_number> [options]'
15
+ c.description = 'Track a package by tracking number'
16
+ c.option '--carrier STRING', String,
17
+ 'Specify the carrier, otherwise carrier is detected'
18
+ c.option '--cookie STRING', String,
19
+ 'Specify cookie data to set when verifying'
20
+ c.option '--verify STRING', String,
21
+ 'Specify the verification string'
22
+ c.action do |args, options|
23
+ tracking_number = args.first
24
+ if tracking_number.nil?
25
+ $stderr.puts("Please specify a tracking number".red)
26
+ exit(1)
27
+ end
28
+ carrier = nil
29
+ if options.carrier.nil?
30
+ err, carrier = PacTrac::Carrier.for_tracking_number(
31
+ tracking_number)
32
+ unless err.valid
33
+ $stderr.puts(
34
+ "Could not match to a carrier, please specify with --carrier".red)
35
+ exit(2)
36
+ end
37
+ else
38
+ begin
39
+ carrier = PacTrac::Carrier.const_get(options.carrier)
40
+ rescue
41
+ $stderr.puts("No carrier with the name #{options.carrier}".red)
42
+ exit(3)
43
+ end
44
+ end
45
+ session = carrier.start_session
46
+ if options.verify.nil?
47
+ err, resp = carrier.tracking_request(tracking_number, session)
48
+ else
49
+ unless options.cookie.nil?
50
+ session.cookies = PacTrac::Http::Cookie.from_request_header_value(
51
+ options.cookie)
52
+ end
53
+ err, resp = carrier.verify(tracking_number, options.verify, session)
54
+ end
55
+ unless err.valid
56
+ $stderr.puts("Error requesting tracking data: #{err.msg}".red)
57
+ exit(4)
58
+ end
59
+ if resp.requires_verification
60
+ $stdout.puts("#{carrier.title} requires verification, please view the " +
61
+ "image at #{resp.verification_image.green} and run pactrac again " +
62
+ "using the following command:")
63
+ $stdout.puts
64
+ cookies = PacTrac::Http::Cookie.from_response(resp)
65
+ cookies_string = cookies.length == 0 ? '' : ' --cookie ' +
66
+ Shellwords.escape(PacTrac::Http::Cookie.to_request_header_value(
67
+ cookies))
68
+ $stdout.puts("pactrac track #{Shellwords.escape(tracking_number)
69
+ } --carrier #{carrier.name.split('::').last}#{cookies_string
70
+ } --verify " + "YOUR_VERIFICATION_HERE".green)
71
+ exit(5)
72
+ else
73
+ err, tracking_data = carrier.parse_tracking_data(resp)
74
+ unless err.valid
75
+ $stderr.puts("Error parsing tracking data: #{err.msg}".red)
76
+ exit(6)
77
+ end
78
+ end
79
+
80
+ # Discovers headings from updates
81
+ headings = [ :at, :location, :message ]
82
+ tracking_data[:updates].each do |u|
83
+ u.keys.each do |key|
84
+ headings << key unless headings.include?(key)
85
+ end
86
+ end
87
+ rows = tracking_data[:updates].sort_by { |u| u[:at] }.reverse.map do |u|
88
+ headings.map { |heading| u[heading] }
89
+ end
90
+
91
+ PacTrac::Http::Session.finish(session)
92
+ $stdout.puts("Tracking number: #{tracking_number}")
93
+ $stdout.puts("Carrier: #{carrier.title}")
94
+ unless tracking_data[:origin].nil?
95
+ $stdout.puts("Origin: #{tracking_data[:origin]}")
96
+ end
97
+ unless tracking_data[:destination].nil?
98
+ $stdout.puts("Destination: #{tracking_data[:destination]}")
99
+ end
100
+ $stdout.puts
101
+
102
+ puts Terminal::Table.new(:headings => headings.map { |h|
103
+ h.to_s.capitalize }, :rows => rows)
104
+ end
105
+ end
106
+
107
+ default_command :help
@@ -0,0 +1,2 @@
1
+ require 'pactrac/carrier'
2
+ require 'pactrac/err'
@@ -0,0 +1,20 @@
1
+ require 'pactrac/carrier/dhl'
2
+ require 'pactrac/carrier/ems'
3
+ require 'pactrac/err'
4
+
5
+ module PacTrac
6
+ module Carrier
7
+ module_function
8
+
9
+ def all
10
+ [Carrier::Dhl, Carrier::Ems]
11
+ end
12
+
13
+ def for_tracking_number(tracking_number)
14
+ all.each do |c|
15
+ return Err.new(true), c if c.tracking_number_relevant?(tracking_number)
16
+ end
17
+ return Err.new(false, 'unable to match tracking number to carrier')
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,81 @@
1
+ require 'nokogiri'
2
+ require 'pactrac/err'
3
+ require 'pactrac/response'
4
+ require 'pactrac/http'
5
+ require 'pactrac/http/session'
6
+ require 'net/http'
7
+ require 'date'
8
+
9
+ module PacTrac
10
+ module Carrier
11
+ module Dhl
12
+ module_function
13
+
14
+ def title
15
+ 'DHL'
16
+ end
17
+
18
+ def tracking_number_relevant?(tracking_number)
19
+ tracking_number.strip.match(/^\d{10}$/)
20
+ end
21
+
22
+ def tracking_request(tracking_number, session)
23
+ err, raw = Http.request(Net::HTTP::Get.new(
24
+ "/content/g0/en/express/tracking.shtml" +
25
+ "?brand=DHL&AWB=#{tracking_number}%0D%0A", 'User-Agent' =>
26
+ 'Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.1 (KHTML, like' +
27
+ ' Gecko) Chrome/21.0.1180.75 Safari/537.1'), session)
28
+ unless err.valid
29
+ return Err.new(false,
30
+ "there was a problem connecting to the DHL server, #{err.msg}")
31
+ end
32
+ return Err.new(true), Response.new(raw, false)
33
+ end
34
+
35
+ def parse_tracking_data(response)
36
+ doc = Nokogiri::HTML(response.raw.body)
37
+ table = doc.css('.clpt_tracking_results table').first
38
+ if table.nil?
39
+ return Err.new(false, 'unable to find tracking data table')
40
+ end
41
+ tracking_data = { :updates => [] }
42
+ origin_node = table.css('#orginURL4').first
43
+ if origin_node.nil?
44
+ return Err.new(false, 'unable to find origin')
45
+ end
46
+ tracking_data[:origin] = origin_node.content
47
+ destination_node = table.css('#destinationURL4').first
48
+ if destination_node.nil?
49
+ return Err.new(false, 'unable to find destination')
50
+ end
51
+ tracking_data[:destination] = destination_node.content
52
+ current_date = nil
53
+ table.children.each do |section|
54
+ next unless ['thead', 'tbody'].include?(section.name.to_s)
55
+ next if section.attribute('class').to_s == 'tophead'
56
+ section.css('tr').each do |row|
57
+ cells = row.css('td, th')
58
+ first_cell = cells.first
59
+ case
60
+ when first_cell.name == 'th' # Date cell
61
+ current_date = Date.parse(first_cell.content)
62
+ when first_cell.attribute('class').to_s != 'emptyRow'
63
+ next if current_date.nil?
64
+ tracking_data[:updates] << {
65
+ :message => cells[1].content.strip,
66
+ :location => cells[2].content.strip,
67
+ :at => DateTime.parse(
68
+ "#{current_date.to_s}T#{cells[3].content.to_s}"),
69
+ }
70
+ end
71
+ end
72
+ end
73
+ return Err.new(true), tracking_data
74
+ end
75
+
76
+ def start_session
77
+ Http::Session.start('www.dhl.com', 80)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,87 @@
1
+ require 'nokogiri'
2
+ require 'pactrac/err'
3
+ require 'pactrac/response'
4
+ require 'pactrac/http'
5
+ require 'pactrac/http/session'
6
+ require 'pactrac/http/cookie'
7
+ require 'pactrac/verify/file'
8
+ require 'net/http'
9
+ require 'date'
10
+
11
+ module PacTrac
12
+ module Carrier
13
+ module Ems
14
+ module_function
15
+
16
+ def title
17
+ 'EMS'
18
+ end
19
+
20
+ def tracking_number_relevant?(tracking_number)
21
+ tracking_number.strip.match(/^EE\d+CN$/)
22
+ end
23
+
24
+ def tracking_request(tracking_number, session)
25
+ err, raw = Http.request(Net::HTTP::Get.new('/english.html',
26
+ 'User-Agent' => user_agent), session)
27
+ return err unless err.valid
28
+ # Get validation image
29
+ err, raw = Http.request(Net::HTTP::Get.new('/ems/rand',
30
+ 'User-Agent' => user_agent), session)
31
+ unless err.valid
32
+ return Err.new(false,
33
+ "there was a problem connecting to the EMS server, #{err.msg}")
34
+ end
35
+ f = Verify::File.create('ems.jpg', raw.body)
36
+ return Err.new(true), Response.new(raw, true, "file://#{f}")
37
+ end
38
+
39
+ def verify(tracking_number, verification, session)
40
+ req = Net::HTTP::Post.new('/ems/order/singleQuery_e',
41
+ 'User-Agent' => user_agent)
42
+ req.set_form_data({ :mailNum => tracking_number, :checkCode =>
43
+ verification })
44
+ if session.cookies
45
+ req['Cookie'] = Http::Cookie.to_request_header_value(session.cookies)
46
+ end
47
+ err, raw = Http.request(req, session)
48
+ unless err.valid
49
+ return Err.new(false,
50
+ "there was a problem connecting to the EMS server, #{err.msg}")
51
+ end
52
+ if raw.body.match(/failure/i)
53
+ return Err.new(false, 'failure to verify using given code')
54
+ end
55
+ return Err.new(true), Response.new(raw, false)
56
+ end
57
+
58
+ def parse_tracking_data(response)
59
+ doc = Nokogiri::HTML(response.raw.body)
60
+ table = doc.css('.mailnum_result_box table')
61
+ if table.nil?
62
+ return Err.new(false, 'unable to find tracking data table')
63
+ end
64
+ tracking_data = { :updates => [] }
65
+ table.css('tr').each do |row|
66
+ cells = row.css('td')
67
+ next if cells.nil? or cells.length < 2
68
+ tracking_data[:updates] << {
69
+ :at => DateTime.parse(cells[0].content.to_s),
70
+ :location => cells[1].content.to_s.strip,
71
+ :message => cells[2].content.to_s.strip,
72
+ }
73
+ end
74
+ return Err.new(true), tracking_data
75
+ end
76
+
77
+ def start_session
78
+ Http::Session.start('www.ems.com.cn', 80)
79
+ end
80
+
81
+ def user_agent
82
+ 'Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.1 (KHTML, like' +
83
+ ' Gecko) Chrome/21.0.1180.75 Safari/537.1'
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,3 @@
1
+ module PacTrac
2
+ Err = Struct.new(:valid, :msg)
3
+ end
@@ -0,0 +1,22 @@
1
+ require 'timeout'
2
+ require 'net/http'
3
+
4
+ module PacTrac
5
+ module Http
6
+ module_function
7
+
8
+ def request(req, session)
9
+ raw = nil
10
+ begin
11
+ Timeout::timeout(10) do
12
+ raw = session.session.request(req)
13
+ end
14
+ rescue Timeout::Error => e
15
+ return Err.new(false, 'tracking request took too long to respond')
16
+ rescue Net::HTTPError => e
17
+ return Err.new(false, 'error making tracking request')
18
+ end
19
+ return Err.new(true), raw
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,58 @@
1
+ require 'uri'
2
+
3
+ module PacTrac
4
+ module Http
5
+ module Cookie
6
+ module_function
7
+
8
+ def from_response(response)
9
+ from_raw(response.raw)
10
+ end
11
+
12
+ def from_raw(raw)
13
+ cookies = {}
14
+ raw.get_fields('Set-Cookie').each do |c|
15
+ key, value = from_response_header_value(c)
16
+ cookies[key] = value
17
+ end
18
+ cookies
19
+ end
20
+
21
+ def from_response_header_value(header_value)
22
+ header_value.split(/\s*;\s*/)[0].split(/\s*=\s*/)[0,2].map {|t|
23
+ URI.unescape(t) }
24
+ end
25
+
26
+ def update_from_response(response, cookies)
27
+ update_from_raw(response.raw, cookies)
28
+ end
29
+
30
+ def update_from_raw(raw, cookies)
31
+ c = cookies.clone
32
+ from_raw(raw).each do |key, value|
33
+ c[key] = value
34
+ end
35
+ c
36
+ end
37
+
38
+ def to_request_header_value(cookies)
39
+ cookies.map { |key, value|
40
+ "#{URI.escape(key, ',;=')}=#{URI.escape(value, ',;=')}"
41
+ }.join('; ')
42
+ end
43
+
44
+ def to_request_header(cookies)
45
+ "Cookie: #{to_request_header_value(cookies)}"
46
+ end
47
+
48
+ def from_request_header_value(header_value)
49
+ header_value.split(/\s*;\s*/).reject{ |v| v.strip == '' }.map { |p|
50
+ p.split(/\s*=\s*/)[0,2].map {|t| URI.unescape(t) }
51
+ }.inject({}) { |hash, (key, value)|
52
+ hash[key] = value
53
+ hash
54
+ }
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,19 @@
1
+ require 'net/http'
2
+
3
+ module PacTrac
4
+ module Http
5
+ module Session
6
+ module_function
7
+
8
+ Store = Struct.new(:session, :cookies)
9
+
10
+ def start(address, port)
11
+ Store.new(Net::HTTP.start(address, port), {})
12
+ end
13
+
14
+ def finish(session)
15
+ session.session.finish
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module PacTrac
2
+ Response = Struct.new(:raw, :requires_verification, :verification_image)
3
+ end
@@ -0,0 +1,17 @@
1
+ require 'tmpdir'
2
+
3
+ module PacTrac
4
+ module Verify
5
+ module File
6
+ module_function
7
+
8
+ def create(name, data)
9
+ filename = ::File.join(Dir.tmpdir, "pactrac_#{Time.now.to_i}_#{name}")
10
+ ::File.open(filename, 'w') do |f|
11
+ f.write(data)
12
+ end
13
+ filename
14
+ end
15
+ end
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pactrac
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Michael Alexander
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-08-23 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: nokogiri
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 9
29
+ segments:
30
+ - 1
31
+ - 5
32
+ - 5
33
+ version: 1.5.5
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: commander
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 63
45
+ segments:
46
+ - 4
47
+ - 1
48
+ - 2
49
+ version: 4.1.2
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: terminal-table
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 13
61
+ segments:
62
+ - 1
63
+ - 4
64
+ - 5
65
+ version: 1.4.5
66
+ type: :runtime
67
+ version_requirements: *id003
68
+ - !ruby/object:Gem::Dependency
69
+ name: colorize
70
+ prerelease: false
71
+ requirement: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ hash: 27
77
+ segments:
78
+ - 0
79
+ - 5
80
+ - 8
81
+ version: 0.5.8
82
+ type: :runtime
83
+ version_requirements: *id004
84
+ description: International package tracking for Ruby
85
+ email: beefsack@gmail.com
86
+ executables:
87
+ - pactrac
88
+ extensions: []
89
+
90
+ extra_rdoc_files: []
91
+
92
+ files:
93
+ - lib/pactrac/carrier/dhl.rb
94
+ - lib/pactrac/carrier/ems.rb
95
+ - lib/pactrac/err.rb
96
+ - lib/pactrac/http.rb
97
+ - lib/pactrac/http/session.rb
98
+ - lib/pactrac/http/cookie.rb
99
+ - lib/pactrac/carrier.rb
100
+ - lib/pactrac/verify/file.rb
101
+ - lib/pactrac/response.rb
102
+ - lib/pactrac.rb
103
+ - bin/pactrac
104
+ - LICENSE
105
+ - README.md
106
+ homepage: https://github.com/Miniand/pactrac
107
+ licenses: []
108
+
109
+ post_install_message:
110
+ rdoc_options: []
111
+
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ hash: 3
120
+ segments:
121
+ - 0
122
+ version: "0"
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ none: false
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ hash: 3
129
+ segments:
130
+ - 0
131
+ version: "0"
132
+ requirements: []
133
+
134
+ rubyforge_project:
135
+ rubygems_version: 1.8.24
136
+ signing_key:
137
+ specification_version: 3
138
+ summary: pactrac
139
+ test_files: []
140
+