shipping_materials 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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +232 -0
- data/Rakefile +1 -0
- data/examples/example.rb +33 -0
- data/lib/shipping_materials.rb +19 -0
- data/lib/shipping_materials/config.rb +35 -0
- data/lib/shipping_materials/csv_dsl.rb +82 -0
- data/lib/shipping_materials/group.rb +27 -0
- data/lib/shipping_materials/mixins/sortable.rb +26 -0
- data/lib/shipping_materials/packager.rb +52 -0
- data/lib/shipping_materials/packing_slips.rb +17 -0
- data/lib/shipping_materials/s3.rb +23 -0
- data/lib/shipping_materials/sorter.rb +49 -0
- data/lib/shipping_materials/storage.rb +54 -0
- data/lib/shipping_materials/version.rb +3 -0
- data/shipping_materials.gemspec +28 -0
- data/test/data.rb +217 -0
- data/test/files/template.mustache +16 -0
- data/test/files/test.html +3 -0
- data/test/models.rb +23 -0
- data/test/suite.rb +6 -0
- data/test/test_helper.rb +9 -0
- data/test/unit/config_test.rb +14 -0
- data/test/unit/csv_dsl_test.rb +117 -0
- data/test/unit/group_test.rb +61 -0
- data/test/unit/packager_test.rb +63 -0
- data/test/unit/packing_slips_test.rb +9 -0
- data/test/unit/s3_test.rb +17 -0
- data/test/unit/sorter_test.rb +48 -0
- data/test/unit/storage_test.rb +37 -0
- metadata +162 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
module ShippingMaterials
|
2
|
+
class Group
|
3
|
+
include Sortable
|
4
|
+
|
5
|
+
attr_accessor :objects, :basename, :csvs
|
6
|
+
|
7
|
+
def initialize(basename, objects)
|
8
|
+
@basename = basename
|
9
|
+
@objects = objects
|
10
|
+
@csvs = []
|
11
|
+
@extension = 'csv'
|
12
|
+
@headers = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def filter(&block)
|
16
|
+
@objects = @objects.select {|o| o.instance_eval(&block) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def csv(options={}, &block)
|
20
|
+
if block
|
21
|
+
csv = CSVDSL.new(@objects, options)
|
22
|
+
csv.instance_eval(&block)
|
23
|
+
@csvs << csv
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ShippingMaterials
|
2
|
+
module Sortable
|
3
|
+
attr_accessor :sorters
|
4
|
+
|
5
|
+
# DSL method
|
6
|
+
def sort(context=:objects, &block)
|
7
|
+
@sorters ||= {}
|
8
|
+
@sorters[context] = Sorter.new
|
9
|
+
@sorters[context].instance_eval(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Perform the sort
|
13
|
+
def sort!
|
14
|
+
@sorters.each do |context, sorter|
|
15
|
+
@objects = if context == :objects
|
16
|
+
sorter.sort(@objects)
|
17
|
+
else
|
18
|
+
@objects.each do |object|
|
19
|
+
object.send(:"#{context}=", sorter.sort(object.send(context)))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
@objects
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module ShippingMaterials
|
2
|
+
class Packager
|
3
|
+
include Sortable
|
4
|
+
|
5
|
+
attr_accessor :objects, :groups, :template
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@groups = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def package(objects, &block)
|
12
|
+
@objects = objects
|
13
|
+
instance_eval(&block)
|
14
|
+
@groups.each do |group|
|
15
|
+
sort_group(group)
|
16
|
+
create_packing_slips(group)
|
17
|
+
csvs(group)
|
18
|
+
end
|
19
|
+
Storage.gzip if Config.use_gzip?
|
20
|
+
end
|
21
|
+
|
22
|
+
def pdf(template)
|
23
|
+
@packing_slip_template = template
|
24
|
+
end
|
25
|
+
|
26
|
+
def group(basename, &block)
|
27
|
+
group = Group.new(basename, @objects)
|
28
|
+
group.instance_eval(&block) if block
|
29
|
+
@groups << group
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def sort_group(group)
|
35
|
+
return unless self.sorters.nil?
|
36
|
+
group.sorters ||= self.sorters unless group.sorters.nil?
|
37
|
+
group.sort!
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_packing_slips(group)
|
41
|
+
packing_slip = PackingSlips.new(group.objects, @packing_slip_template)
|
42
|
+
Storage.write_pdf(group.basename, packing_slip.to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
def csvs(group)
|
46
|
+
group.csvs.each do |csv|
|
47
|
+
extension = group.basename + '.' + csv.extension
|
48
|
+
Storage.write_file(extension, csv.to_s)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ShippingMaterials
|
2
|
+
class PackingSlips
|
3
|
+
def initialize(objects, template_file)
|
4
|
+
@objects = objects
|
5
|
+
@template_file = template_file
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
t = Mustache.new
|
10
|
+
t.template_file = @template_file
|
11
|
+
t[Config.base_context] = @objects
|
12
|
+
t.render
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :to_html, :to_s
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ShippingMaterials
|
2
|
+
class S3
|
3
|
+
begin
|
4
|
+
require 'aws-sdk'
|
5
|
+
rescue LoadError => e
|
6
|
+
e.message << " (You may need to install the aws-sdk gem)"
|
7
|
+
raise e
|
8
|
+
end unless defined?(::AWS::Core)
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@bucket = self.s3.buckets[Config.s3_bucket]
|
12
|
+
end
|
13
|
+
|
14
|
+
def write(key, fp)
|
15
|
+
@bucket.objects.create(key, fp)
|
16
|
+
end
|
17
|
+
|
18
|
+
def s3
|
19
|
+
@s3 ||= AWS::S3.new(access_key_id: Config.s3_access_key,
|
20
|
+
secret_access_ket: Config.s3_secret)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ShippingMaterials
|
2
|
+
class Sorter
|
3
|
+
def initialize
|
4
|
+
@rules = []
|
5
|
+
@attr_callbacks = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def rule(&block)
|
9
|
+
@rules << block
|
10
|
+
end
|
11
|
+
|
12
|
+
# This is suiting MY purpose for now
|
13
|
+
# The plan is to implement chainable callbacks
|
14
|
+
def each_by(attr)
|
15
|
+
@attr_callbacks << attr
|
16
|
+
end
|
17
|
+
|
18
|
+
def sort(items, i=0)
|
19
|
+
return items if !@rules[i] || items.size < 2
|
20
|
+
|
21
|
+
a, b = [], []
|
22
|
+
|
23
|
+
items.each do |item|
|
24
|
+
if item.instance_eval(&@rules[i])
|
25
|
+
a << item
|
26
|
+
else
|
27
|
+
b << item
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
apply_callbacks(a)
|
32
|
+
apply_callbacks(b)
|
33
|
+
|
34
|
+
i += 1
|
35
|
+
( sort(a, i) + sort(b, i) ).compact
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def apply_callbacks(items)
|
42
|
+
@attr_callbacks.each do |attr|
|
43
|
+
items.sort! do |a,b|
|
44
|
+
b.send(attr) <=> a.send(attr)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module ShippingMaterials
|
2
|
+
class Storage
|
3
|
+
class << self
|
4
|
+
def write_file(filename, contents)
|
5
|
+
filename = "#{save_path}/#{filename}"
|
6
|
+
File.open(filename, 'w') do |fp|
|
7
|
+
fp.write(contents)
|
8
|
+
if Config.use_s3? && !Config.use_gzip?
|
9
|
+
@s3 ||= S3.new
|
10
|
+
@s3.write(filename, fp)
|
11
|
+
File.unlink(fp)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def write_pdf(filename, contents)
|
17
|
+
basename = filenameize(File.basename(filename, '.*'))
|
18
|
+
base = "#{save_path}/#{basename}"
|
19
|
+
html_file, pdf_file = base + '.html', base + '.pdf'
|
20
|
+
File.open(html_file, 'w') {|f| f.write(contents) }
|
21
|
+
%x( wkhtmltopdf #{html_file} #{pdf_file} )
|
22
|
+
File.unlink(html_file)
|
23
|
+
if Config.use_s3? && !Config.use_gzip?
|
24
|
+
@s3 ||= S3.new
|
25
|
+
File.open(pdf_file) do |fp|
|
26
|
+
@s3.write(File.basename(pdf_file), fp)
|
27
|
+
end
|
28
|
+
File.unlink(pdf_file)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def gzip
|
33
|
+
filename = "#{Config.save_path}/#{Config.gzip_file_name}"
|
34
|
+
`cd #{Config.save_path} && tar -cvzf #{filename} * && cd -`
|
35
|
+
if Config.use_s3?
|
36
|
+
@s3 ||= S3.new
|
37
|
+
File.open(filename) do |fp|
|
38
|
+
@s3.write(Config.gzip_file_name, fp)
|
39
|
+
end
|
40
|
+
FileUtils.rm_rf(self.save_path) unless self.save_path == '/'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def save_path
|
45
|
+
FileUtils.mkdir(Config.save_path) unless Dir.exists?(Config.save_path)
|
46
|
+
Config.save_path
|
47
|
+
end
|
48
|
+
|
49
|
+
def filenameize(string)
|
50
|
+
string.gsub(/[^A-Z0-9_]+/i, '')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'shipping_materials/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'shipping_materials'
|
8
|
+
spec.version = ShippingMaterials::VERSION
|
9
|
+
spec.authors = ['Andrew Haust']
|
10
|
+
spec.email = ['andrewwhhaust@gmail.com']
|
11
|
+
spec.description = %q{DSL for creating packing slips
|
12
|
+
and general shipping materials}
|
13
|
+
spec.summary = %q{Shipping Materials}
|
14
|
+
spec.homepage = "http://www.github.com/sodapopcan/shipping_materials"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split("\n")
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) {|f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_dependency 'mustache'
|
23
|
+
spec.add_dependency 'aws-sdk'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
26
|
+
spec.add_development_dependency 'rake'
|
27
|
+
spec.add_development_dependency 'debugger'
|
28
|
+
end
|
data/test/data.rb
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
require File.expand_path('../models', __FILE__)
|
2
|
+
|
3
|
+
module TestData
|
4
|
+
|
5
|
+
def orders
|
6
|
+
@orders ||= get_orders
|
7
|
+
end
|
8
|
+
|
9
|
+
def get_orders
|
10
|
+
orders = []
|
11
|
+
|
12
|
+
o = TestModels::Order.new
|
13
|
+
o.id = 1
|
14
|
+
o.name = 'Andrew'
|
15
|
+
o.address = '207 Lawrence Ave E'
|
16
|
+
o.email = 'andrew@gelaskins.com'
|
17
|
+
o.phone = '416-555-0634'
|
18
|
+
o.country = 'CA'
|
19
|
+
o.shipping_method = 'UPS'
|
20
|
+
|
21
|
+
li = TestModels::LineItem.new
|
22
|
+
li.id = 1
|
23
|
+
li.name = 'Plague Soundscapes'
|
24
|
+
li.type = 'Vinyl'
|
25
|
+
li.quantity = 3
|
26
|
+
li.price = 15.95
|
27
|
+
li.variant = TestModels::Variant.new(name: 'plague soundscapes')
|
28
|
+
o.line_items << li
|
29
|
+
|
30
|
+
li = TestModels::LineItem.new
|
31
|
+
li.id = 2
|
32
|
+
li.name = 'Surfer Rosa'
|
33
|
+
li.type = 'Vinyl'
|
34
|
+
li.quantity = 1
|
35
|
+
li.price = 37.24
|
36
|
+
li.variant = TestModels::Variant.new(name: 'surfer rosa')
|
37
|
+
o.line_items << li
|
38
|
+
|
39
|
+
orders << o
|
40
|
+
|
41
|
+
|
42
|
+
o = TestModels::Order.new
|
43
|
+
o.id = 2
|
44
|
+
o.name = 'Julie'
|
45
|
+
o.address = '17 Whatever St'
|
46
|
+
o.email = 'julie@email.com'
|
47
|
+
o.phone = '232-555-0001'
|
48
|
+
o.country = 'US'
|
49
|
+
o.shipping_method = 'std'
|
50
|
+
|
51
|
+
li = TestModels::LineItem.new
|
52
|
+
li.id = 3
|
53
|
+
li.name = 'Analphabetapolothology'
|
54
|
+
li.type = 'Vinyl'
|
55
|
+
li.quantity = 1
|
56
|
+
li.price = 20.45
|
57
|
+
o.line_items << li
|
58
|
+
|
59
|
+
orders << o
|
60
|
+
|
61
|
+
|
62
|
+
o = TestModels::Order.new
|
63
|
+
o.id = 3
|
64
|
+
o.name = 'Derek'
|
65
|
+
o.address = '17 Whatever St'
|
66
|
+
o.email = 'derek@email.com'
|
67
|
+
o.phone = '232-555-0001'
|
68
|
+
o.country = 'US'
|
69
|
+
o.shipping_method = 'std'
|
70
|
+
|
71
|
+
li = TestModels::LineItem.new
|
72
|
+
li.id = 4
|
73
|
+
li.name = 'The Immaculate Collection'
|
74
|
+
li.type = 'Vinyl'
|
75
|
+
li.quantity = 3
|
76
|
+
li.price = 20
|
77
|
+
o.line_items << li
|
78
|
+
|
79
|
+
li = TestModels::LineItem.new
|
80
|
+
li.id = 9
|
81
|
+
li.name = '()'
|
82
|
+
li.type = 'CD'
|
83
|
+
li.quantity = 2
|
84
|
+
li.price = 20
|
85
|
+
o.line_items << li
|
86
|
+
|
87
|
+
li = TestModels::LineItem.new
|
88
|
+
li.id = 10
|
89
|
+
li.name = 'Siamese Dream'
|
90
|
+
li.type = 'CD'
|
91
|
+
li.quantity = 5
|
92
|
+
li.price = 20
|
93
|
+
o.line_items << li
|
94
|
+
|
95
|
+
li = TestModels::LineItem.new
|
96
|
+
li.id = 11
|
97
|
+
li.name = 'Parklife'
|
98
|
+
li.type = 'CD'
|
99
|
+
li.quantity = 1
|
100
|
+
li.price = 20
|
101
|
+
o.line_items << li
|
102
|
+
|
103
|
+
li = TestModels::LineItem.new
|
104
|
+
li.id = 12
|
105
|
+
li.name = 'The Eldrich Dark'
|
106
|
+
li.type = 'Vinyl'
|
107
|
+
li.quantity = 1
|
108
|
+
li.price = 20
|
109
|
+
o.line_items << li
|
110
|
+
|
111
|
+
li = TestModels::LineItem.new
|
112
|
+
li.id = 13
|
113
|
+
li.name = 'Totally Krossed Out'
|
114
|
+
li.type = 'Cassette'
|
115
|
+
li.quantity = 1
|
116
|
+
li.price = 8
|
117
|
+
o.line_items << li
|
118
|
+
|
119
|
+
li = TestModels::LineItem.new
|
120
|
+
li.id = 14
|
121
|
+
li.name = 'Schubert Dip'
|
122
|
+
li.type = 'Cassette'
|
123
|
+
li.quantity = 1
|
124
|
+
li.price = 12
|
125
|
+
o.line_items << li
|
126
|
+
|
127
|
+
li = TestModels::LineItem.new
|
128
|
+
li.id = 15
|
129
|
+
li.name = 'Please Hammer, Don\'t Hurt \'Em'
|
130
|
+
li.type = 'CD'
|
131
|
+
li.quantity = 10
|
132
|
+
li.price = 12
|
133
|
+
o.line_items << li
|
134
|
+
|
135
|
+
orders << o
|
136
|
+
|
137
|
+
|
138
|
+
o = TestModels::Order.new
|
139
|
+
o.id = 4
|
140
|
+
o.name = 'Riley'
|
141
|
+
o.address = '17 Whatever St'
|
142
|
+
o.email = 'riley@email.com'
|
143
|
+
o.phone = '416-555-0987'
|
144
|
+
o.country = 'CA'
|
145
|
+
o.shipping_method = 'UPSexp'
|
146
|
+
|
147
|
+
li = TestModels::LineItem.new
|
148
|
+
li.id = 5
|
149
|
+
li.name = 'Don Caballero 2'
|
150
|
+
li.type = 'CD'
|
151
|
+
li.quantity = 1
|
152
|
+
li.price = 12
|
153
|
+
o.line_items << li
|
154
|
+
|
155
|
+
orders << o
|
156
|
+
|
157
|
+
|
158
|
+
o = TestModels::Order.new
|
159
|
+
o.id = 4
|
160
|
+
o.name = 'Miller'
|
161
|
+
o.address = '17 Whatever St'
|
162
|
+
o.email = 'miller@email.com'
|
163
|
+
o.phone = '416-555-0987'
|
164
|
+
o.country = 'CA'
|
165
|
+
o.shipping_method = 'UPSexp'
|
166
|
+
|
167
|
+
li = TestModels::LineItem.new
|
168
|
+
li.id = 6
|
169
|
+
li.name = 'Confield'
|
170
|
+
li.type = 'CD'
|
171
|
+
li.quantity = 1
|
172
|
+
li.price = 12
|
173
|
+
o.line_items << li
|
174
|
+
|
175
|
+
orders << o
|
176
|
+
|
177
|
+
|
178
|
+
o = TestModels::Order.new
|
179
|
+
o.id = 5
|
180
|
+
o.name = 'Jan'
|
181
|
+
o.address = '45 Bergen Strasse'
|
182
|
+
o.email = 'jan@email.com'
|
183
|
+
o.phone = '876-555-0987'
|
184
|
+
o.country = 'DE'
|
185
|
+
o.shipping_method = 'UPSexp'
|
186
|
+
|
187
|
+
li = TestModels::LineItem.new
|
188
|
+
li.id = 7
|
189
|
+
li.name = 'Living with the Ancients'
|
190
|
+
li.type = 'Vinyl'
|
191
|
+
li.quantity = 1
|
192
|
+
li.price = 12
|
193
|
+
o.line_items << li
|
194
|
+
|
195
|
+
orders << o
|
196
|
+
|
197
|
+
|
198
|
+
o = TestModels::Order.new
|
199
|
+
o.id = 6
|
200
|
+
o.name = 'J.M.'
|
201
|
+
o.address = '1233 St. Clair Ave W'
|
202
|
+
o.email = 'jm@email.com'
|
203
|
+
o.phone = '876-555-0987'
|
204
|
+
o.country = 'CA'
|
205
|
+
o.shipping_method = 'std'
|
206
|
+
|
207
|
+
li = TestModels::LineItem.new
|
208
|
+
li.id = 8
|
209
|
+
li.name = 'Vistavision'
|
210
|
+
li.type = 'Vinyl'
|
211
|
+
li.quantity = 5
|
212
|
+
li.price = 12
|
213
|
+
o.line_items << li
|
214
|
+
|
215
|
+
orders << o
|
216
|
+
end
|
217
|
+
end
|