pactrac 0.0.1

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/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
+