mswallet 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ require 'mswallet/version'
2
+ require 'rack/mswallet_rack'
3
+
4
+ module Mswallet
5
+ autoload :Pass, 'mswallet/pass'
6
+
7
+ class << self
8
+ attr_accessor :custom_rack_handler
9
+ def configure
10
+ yield self
11
+ end
12
+ end
13
+
14
+ end
@@ -0,0 +1,187 @@
1
+ require 'xml/libxml'
2
+ require 'zip'
3
+
4
+ module Mswallet
5
+ class Pass
6
+ TYPES = %W(Deal General PaymentInstrument Ticket BoardingPass MembershipCard )
7
+
8
+ attr_accessor :pass, :files, :locales
9
+
10
+ def initialize(pass = nil)
11
+ @pass = pass || self.class.init
12
+ @locales = {}
13
+ @files = []
14
+ yield(self) if block_given?
15
+ end
16
+
17
+ def []=(key, val)
18
+ result = convert_to_xml(key, val)
19
+ if result.is_a? Array
20
+ result.each { |item| @pass.root << item }
21
+ else
22
+ @pass.root << result
23
+ end
24
+ end
25
+
26
+ def [](key)
27
+ @pass.root.find(key).first
28
+ end
29
+
30
+ def add_file(file)
31
+ @files << file
32
+ end
33
+
34
+ def add_locale(lang, file)
35
+ @locales[lang] = file
36
+ end
37
+
38
+ # Return a Tempfile containing our ZipStream
39
+ def file(options = {})
40
+ options[:file_name] ||= 'pass.mswallet'
41
+ temp_file = Tempfile.new(options[:file_name])
42
+ temp_file.write self.stream.string
43
+ temp_file.close
44
+ temp_file
45
+ end
46
+ # Return a ZipOutputStream
47
+ def stream
48
+ check_pass
49
+ output_zip
50
+ end
51
+
52
+ def self.init
53
+ doc = XML::Document.new
54
+ doc.encoding = XML::Encoding::UTF_8
55
+ root = XML::Node.new 'WalletItem'
56
+ doc.root = root
57
+
58
+ version_el = XML::Node.new('Version')
59
+ version_el.content = WALLET_VERSION
60
+ root << version_el
61
+
62
+ return doc
63
+ end
64
+
65
+ def valid?
66
+ check_pass
67
+ true
68
+ rescue
69
+ false
70
+ end
71
+
72
+ private
73
+
74
+ def convert_to_xml(key, val)
75
+ case val
76
+ when Hash
77
+ el = XML::Node.new(key)
78
+ val.each do |k,v|
79
+ res = convert_to_xml(k, v)
80
+ if res.is_a? Array
81
+ res.each {|item| el << item }
82
+ else
83
+ el << res
84
+ end
85
+ end
86
+ when Array
87
+ el = []
88
+ val.each do |v|
89
+ el << convert_to_xml(key, v)
90
+ end
91
+ else
92
+ el = XML::Node.new(key)
93
+ if val
94
+ el.content = val.to_s
95
+ else
96
+ text_node = XML::Node.new_text('&#160;')
97
+ text_node.output_escaping = false
98
+ el << text_node
99
+ end
100
+
101
+ end
102
+ el
103
+ end
104
+
105
+ def check_pass
106
+ # Check for default images
107
+ [99, 159, 336].each do |size|
108
+ raise "Logo#{size}x#{size} missing" unless @files.detect {|f| f[:name] == "Logo#{size}x#{size}.png" }
109
+ end
110
+
111
+ # Check for developer field in XML
112
+
113
+ fail 'Pass must be XML::Document object' unless @pass.is_a? XML::Document
114
+
115
+ root = @pass.find('WalletItem')
116
+
117
+ fail 'WalletItem node missing' unless root
118
+
119
+ {
120
+ 'Version' => ['Format Version missing', Mswallet::WALLET_VERSION ],
121
+ 'Kind' => ['Pass Kind Identifier missing', TYPES ],
122
+ 'Id' => ['Id missing'],
123
+ 'DisplayName' => ['Display Name missing' ],
124
+ 'IssuerDisplayName' => ['Issuer Display Name missing'],
125
+ 'HeaderColor' => ['Header Color missing', /^#(?:[\da-fA-F]{2}){3}$/],
126
+ 'BodyColor' => ['Body Name missing', /#(?:[\d\w]){3}/]
127
+
128
+ }.each do |tag, params|
129
+ msg, value = params
130
+ el = @pass.find(tag).first
131
+ fail msg unless el
132
+
133
+ case value
134
+ when Array
135
+ fail "#{tag} not in #{value.join(',')}" unless value.include?(el.content) unless value.empty?
136
+ when Regexp
137
+ fail "#{tag} not match #{value.to_s}" unless el.content.match value
138
+ when nil
139
+ nil
140
+ else
141
+ fail "#{tag} must be #{value}" unless value == el.content
142
+ end
143
+ end
144
+
145
+ end
146
+
147
+ def output_zip
148
+ buffer = Zip::OutputStream.write_buffer do |zip|
149
+ zip.put_next_entry 'WalletItem.xml'
150
+ zip.write @pass.to_s
151
+
152
+ @files.each do |file|
153
+ if file.class == Hash
154
+ zip.put_next_entry file[:name]
155
+ zip.print file[:content]
156
+ else
157
+ zip.put_next_entry File.basename(file)
158
+ zip.print IO.read(file)
159
+ end
160
+ end
161
+
162
+ @locales.each do |lang, file|
163
+ zip.put_next_entry "#{lang}/strings.txt"
164
+
165
+ data = ''
166
+ case file
167
+ when String
168
+ data = file
169
+ when File
170
+ data = file.read
171
+ when Hash
172
+ file.each do |k, v|
173
+ data += "#{k}=#{v}\n"
174
+ end
175
+ else
176
+ fail 'wrong locale object'
177
+ end
178
+
179
+ zip.print data
180
+ end
181
+ end
182
+
183
+ buffer
184
+ end
185
+
186
+ end
187
+ end
@@ -0,0 +1,5 @@
1
+ module Mswallet
2
+ VERSION = "0.0.1"
3
+ WALLET_VERSION = '1'
4
+ RACK_API_VERSION = 'v1'
5
+ end
@@ -0,0 +1,52 @@
1
+ module Rack
2
+ class MswalletRack
3
+
4
+ def initialize(app)
5
+ @app = app
6
+ @parameters = {}
7
+ end
8
+
9
+ def call(env)
10
+ @parameters['authToken'] = env['HTTP_AUTHENTICATIONTOKEN'] if env['HTTP_AUTHENTICATIONTOKEN']
11
+ @parameters.merge!(Rack::Utils.parse_nested_query(env['QUERY_STRING']))
12
+ @parameters['path'] = env['PATH_INFO']
13
+ serial_number = find_id @parameters['path']
14
+ @parameters['serialNumber'] = serial_number
15
+ if serial_number
16
+ handler = Mswallet.custom_rack_handler || Mswallet::Handler
17
+ response = handler.update(@parameters)
18
+ header = {'Content-Type' => 'application/vnd.ms.wallet',
19
+ 'Content-Disposition' => 'attachment',
20
+ 'filename' => "#{serial_number}.mswallet"}
21
+ case response
22
+ when Mswallet::Pass
23
+ [200, header, [response.stream.string]]
24
+ when String
25
+ [200, header, [response]]
26
+ when File, StringIO, Zip::OutputStream, Tempfile
27
+ [200, header, [response.read]]
28
+ when false
29
+ [304, {}, {}]
30
+ else
31
+ [401, {}, {}]
32
+ end
33
+
34
+ else
35
+ @app.call env
36
+ end
37
+ end
38
+
39
+ def append_parameter_separator url
40
+ end
41
+
42
+ def each(&block)
43
+ end
44
+
45
+ def find_id(path)
46
+ return nil unless path =~ /\/#{Mswallet::RACK_API_VERSION}\/walletitems\/([\d\w]+)/
47
+ $1
48
+ end
49
+
50
+ end
51
+ end
52
+
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mswallet/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'mswallet'
8
+ spec.version = Mswallet::VERSION
9
+ spec.authors = ['ajieks@vmp.ru']
10
+ spec.email = %w(ajieks@vmp.ru schalexe@gmail.com)
11
+ spec.summary = %q{A Windows Phone wallet pass generator.}
12
+ spec.description = %q{This gem allows you to create passes for Windows Phone Wallet. Unlike some, this works with Rails but does not require it.}
13
+ spec.homepage = 'http://github.com/fuCtor/mswallet'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler'
22
+ spec.add_development_dependency 'pry'
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_development_dependency 'rspec'
25
+ spec.add_development_dependency 'rack-test', ['>= 0']
26
+ spec.add_development_dependency 'simplecov'
27
+ spec.add_dependency 'libxml-ruby'
28
+ spec.add_dependency 'rack'
29
+ spec.add_dependency('rubyzip', [">= 1.0.0"])
30
+
31
+ end
@@ -0,0 +1,8 @@
1
+ require 'rspec'
2
+
3
+ describe Mswallet do
4
+
5
+ it 'should be wallet version eq 1' do
6
+ expect(Mswallet::WALLET_VERSION).to be_eql('1')
7
+ end
8
+ end
@@ -0,0 +1,190 @@
1
+ require 'rspec'
2
+
3
+ describe Mswallet::Pass do
4
+
5
+ let :fields do
6
+ {
7
+ 'Version' => ['Format Version missing', '1' ],
8
+ 'Kind' => ['Pass Kind Identifier missing', 'General' ],
9
+ 'Id' => ['Id missing', '1'],
10
+ 'DisplayName' => ['Display Name missing' ],
11
+ 'IssuerDisplayName' => ['Issuer Display Name missing'],
12
+ 'HeaderColor' => ['Header Color missing', %W(#000000 #AABBCC)],
13
+ 'BodyColor' => ['Body Name missing', %W(#000000 #AABBCC)]
14
+ }
15
+ end
16
+
17
+ let :wallet do
18
+ w = Mswallet::Pass.init
19
+ fields.each do |*args|
20
+ tag, msg, *value = args.flatten.map(&:to_s)
21
+ el = XML::Node.new(tag)
22
+ w.root << el
23
+
24
+ case value
25
+ when Array
26
+ el.content = value.first.to_s
27
+ when nil
28
+
29
+ else
30
+ el.content = value
31
+ end
32
+ end
33
+ w
34
+ end
35
+ let :pass do Mswallet::Pass.new wallet end
36
+
37
+ it '#init' do
38
+ expect { wallet } .to_not raise_error
39
+ expect( wallet ).to be_instance_of(XML::Document)
40
+ expect( wallet.root.name ).to be_eql 'WalletItem'
41
+ expect( wallet.root.find('Version').empty? ).to be_falsey
42
+ expect( wallet.root.find('Version').first.content ).to be_eql(Mswallet::WALLET_VERSION)
43
+ end
44
+
45
+ it '#add_file' do
46
+ expect { pass } .to_not raise_error
47
+ expect( pass.files ).to be_empty
48
+ expect { pass.add_file name: 'Logo336x336.png', content: '' } .to_not raise_error
49
+ expect( pass.files ).to_not be_empty
50
+ end
51
+
52
+ context 'locale' do
53
+ it '#add_locale' do
54
+
55
+ expect { pass } .to_not raise_error
56
+ expect( pass.locales ).to be_empty
57
+ expect { pass.add_locale 'ru-RU', '' } .to_not raise_error
58
+ expect { pass.add_locale 'ru-RU', {} } .to_not raise_error
59
+ expect { pass.add_locale 'ru-RU', File.new(__FILE__, 'r') } .to_not raise_error
60
+ expect { pass.add_locale 'ru-RU', true } .to_not raise_error
61
+
62
+ expect( pass.locales ).to_not be_empty
63
+ expect( pass.locales['ru-RU'] ).to be_truthy
64
+ end
65
+
66
+ context 'serialize' do
67
+ before do
68
+ [99, 159, 336].each do |size|
69
+ pass.add_file name: "Logo#{size}x#{size}.png", content: ''
70
+ end
71
+
72
+ pass.add_locale 'ru-RU', locale
73
+ end
74
+
75
+ context 'from string' do
76
+ let(:locale) { '' }
77
+ it do
78
+ expect { pass.stream } .to_not raise_error
79
+ end
80
+ end
81
+
82
+ context 'from file' do
83
+ let(:locale) { File.new(__FILE__, 'r') }
84
+ it do
85
+ expect { pass.stream } .to_not raise_error
86
+ end
87
+ end
88
+
89
+ context 'from hash' do
90
+ let(:locale) { {} }
91
+ it do
92
+ expect { pass.stream } .to_not raise_error
93
+ end
94
+ end
95
+
96
+ context 'from other' do
97
+ let(:locale) { nil }
98
+ it do
99
+ expect { pass.stream } .to raise_error 'wrong locale object'
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+
106
+ it '#pass_check' do
107
+ expect { pass } .to_not raise_error
108
+
109
+ [99, 159, 336].each do |size|
110
+ expect( pass.valid? ) .to be_falsey
111
+ expect { pass.stream } .to raise_error /Logo#{size}x#{size}/
112
+ expect { pass.add_file name: "Logo#{size}x#{size}.png", content: '' } .to_not raise_error
113
+ end
114
+
115
+ fields.keys.reverse do |tag|
116
+ msg, *value = fields[tag].map(&:to_s)
117
+ expect { pass.stream } .to_not raise_error msg
118
+
119
+ el = wallet.root.find(tag).first
120
+
121
+ if value
122
+ el.content = ''
123
+ expect { pass.stream } .to raise_error /#{tag}/
124
+ end
125
+
126
+ el.remove!
127
+ expect { pass.stream } .to raise_error msg
128
+ end
129
+ expect( pass.valid? ) .to be_truthy
130
+ end
131
+
132
+
133
+ context '#[]=' do
134
+ let(:key) {'key'}
135
+ after do
136
+ expect(wallet.root.find(key).first).to be_nil
137
+
138
+ expect{ pass[key] = value }.to_not raise_error
139
+
140
+ expect(wallet.root.find(key).first).to_not be_nil
141
+ expect(pass[key]).to be_instance_of XML::Node
142
+ end
143
+
144
+ context 'string value' do
145
+ let(:value) { 'content' }
146
+ it do; end
147
+ end
148
+
149
+ context 'hash value' do
150
+ let(:value) { { 'key2' => 'content', key3: 'content'} }
151
+ it do; end
152
+ end
153
+
154
+ context 'string array value' do
155
+ let(:value) { %w(a b c) }
156
+ it do; end
157
+ end
158
+
159
+ context 'hash array value' do
160
+ let(:value) { [{a: 1},{b: 2}] }
161
+ it do; end
162
+ end
163
+
164
+ context 'nil value' do
165
+ let(:value) { nil }
166
+ it do; end
167
+ end
168
+
169
+ end
170
+
171
+ context 'valid pass' do
172
+ before do
173
+ [99, 159, 336].each do |size|
174
+ pass.add_file name: "Logo#{size}x#{size}.png", content: ''
175
+ end
176
+
177
+ pass.add_locale 'ru-RU', ''
178
+ end
179
+
180
+ it '#stream' do
181
+ expect { pass.stream } .to_not raise_error
182
+ expect( pass.stream.string.empty? ) .to be_falsey
183
+ end
184
+
185
+ it '#file' do
186
+ expect { pass.file } .to_not raise_error
187
+ end
188
+ end
189
+
190
+ end