mswallet 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.
@@ -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