packlist 0.9.0
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 +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +131 -0
- data/Rakefile +12 -0
- data/app/assets/images/_consumed.svg.erb +5 -0
- data/app/assets/images/_worn.svg.erb +3 -0
- data/app/assets/stylesheets/_report.scss +201 -0
- data/app/assets/stylesheets/_system-font.scss +57 -0
- data/app/assets/stylesheets/styles.scss +2 -0
- data/app/assets/templates/report.html.erb +124 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/docs/sample_pack.png +0 -0
- data/docs/sample_pack.rb +68 -0
- data/exe/packlist +16 -0
- data/lib/packlist.rb +4 -0
- data/lib/packlist/dsl.rb +81 -0
- data/lib/packlist/model.rb +178 -0
- data/lib/packlist/report.rb +18 -0
- data/lib/packlist/template.rb +55 -0
- data/lib/packlist/utils.rb +20 -0
- data/lib/packlist/version.rb +3 -0
- data/packlist.gemspec +31 -0
- data/test.html +81 -0
- metadata +182 -0
@@ -0,0 +1,124 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Packlist - <%= packlist.name %></title>
|
5
|
+
<style type="text/css">
|
6
|
+
<%= partial '../stylesheets/system-font' %>
|
7
|
+
<%= partial '../stylesheets/report' %>
|
8
|
+
</style>
|
9
|
+
</head>
|
10
|
+
<body>
|
11
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display: none">
|
12
|
+
<%= partial "../images/worn.svg" %>
|
13
|
+
<%= partial "../images/consumed.svg" %>
|
14
|
+
</svg>
|
15
|
+
|
16
|
+
<header>
|
17
|
+
<h1><%= packlist.name %></h1>
|
18
|
+
<p><%= packlist.description %></p>
|
19
|
+
</header>
|
20
|
+
|
21
|
+
<section>
|
22
|
+
<div class="summary">
|
23
|
+
<table>
|
24
|
+
<thead>
|
25
|
+
<tr>
|
26
|
+
<th>Category</th>
|
27
|
+
<th>Weight</th>
|
28
|
+
</tr>
|
29
|
+
</thead>
|
30
|
+
<tfoot>
|
31
|
+
<tr>
|
32
|
+
<td>Base</td>
|
33
|
+
<td class="weight"><%= packlist.base_weight.lb %></td>
|
34
|
+
</tr>
|
35
|
+
<tr>
|
36
|
+
<td>Consumable</td>
|
37
|
+
<td class="weight"><%= packlist.consumable_weight.lb %></td>
|
38
|
+
</tr>
|
39
|
+
<tr>
|
40
|
+
<td>Worn</td>
|
41
|
+
<td class="weight"><%= packlist.worn_weight.lb %></td>
|
42
|
+
</tr>
|
43
|
+
<tr>
|
44
|
+
<td>Max Pack Weight</td>
|
45
|
+
<td class="weight"><%= packlist.total_pack_weight.lb %></td>
|
46
|
+
</tr>
|
47
|
+
<tr>
|
48
|
+
<td>Total</td>
|
49
|
+
<td class="weight"><%= packlist.total_weight.lb %></td>
|
50
|
+
</tr>
|
51
|
+
</tfoot>
|
52
|
+
<tbody>
|
53
|
+
<% for category in packlist %>
|
54
|
+
<tr>
|
55
|
+
<td><%= category.name %></td>
|
56
|
+
<td><%= category.total_weight.lb %></td>
|
57
|
+
</tr>
|
58
|
+
<% end %>
|
59
|
+
</tbody>
|
60
|
+
</table>
|
61
|
+
</div>
|
62
|
+
|
63
|
+
<div class="categories">
|
64
|
+
<% for category in packlist %>
|
65
|
+
<div class="category">
|
66
|
+
<h2><%= category.name %></h2>
|
67
|
+
|
68
|
+
<table>
|
69
|
+
<tfoot>
|
70
|
+
<tr>
|
71
|
+
<td colspan="2"></td>
|
72
|
+
<td class="subtotal" colspan="3">Subtotal</td>
|
73
|
+
<td class="numeric">
|
74
|
+
<%= category.total_weight.g %>
|
75
|
+
</td>
|
76
|
+
<td class="numeric">
|
77
|
+
<%= category.total_weight.lb %>
|
78
|
+
</td>
|
79
|
+
</tr>
|
80
|
+
</tfoot>
|
81
|
+
<tbody>
|
82
|
+
<% for item in category %>
|
83
|
+
<tr>
|
84
|
+
<td>
|
85
|
+
<input type="checkbox">
|
86
|
+
</td>
|
87
|
+
<td class="item-type">
|
88
|
+
<% if item.worn? %>
|
89
|
+
<svg class="icon worn">
|
90
|
+
<use xlink:href="#worn" />
|
91
|
+
</svg>
|
92
|
+
<% elsif item.consumable? %>
|
93
|
+
<svg class="icon consumable">
|
94
|
+
<use xlink:href="#consumed" />
|
95
|
+
</svg>
|
96
|
+
<% end %>
|
97
|
+
</td>
|
98
|
+
<td class="item-name">
|
99
|
+
<%= item.name %>
|
100
|
+
</td>
|
101
|
+
<td class="item-description">
|
102
|
+
<%= item.description %>
|
103
|
+
</td>
|
104
|
+
<td class="quantity">
|
105
|
+
<% if item.quantity != 1 %>
|
106
|
+
<%= item.quantity %>
|
107
|
+
<% end %>
|
108
|
+
</td>
|
109
|
+
<td class="item-weight">
|
110
|
+
<%= item.total_weight.g %>
|
111
|
+
</td>
|
112
|
+
<td class="item-weight">
|
113
|
+
<%= item.total_weight.oz %>
|
114
|
+
</td>
|
115
|
+
</tr>
|
116
|
+
<% end %>
|
117
|
+
</tbody>
|
118
|
+
</table>
|
119
|
+
</div>
|
120
|
+
<% end %>
|
121
|
+
</div>
|
122
|
+
</section>
|
123
|
+
</body>
|
124
|
+
</html>
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "packlist"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
require "pry"
|
11
|
+
Pry.start
|
12
|
+
|
13
|
+
# require "irb"
|
14
|
+
# IRB.start
|
data/bin/setup
ADDED
Binary file
|
data/docs/sample_pack.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
packlist do
|
2
|
+
name "Epic Backpacking Trip"
|
3
|
+
description "Seven nights in the Sierras, September 2016"
|
4
|
+
|
5
|
+
category "Shelter and sleeping" do
|
6
|
+
# Weight for this item is 280 grams (280.g)
|
7
|
+
item "Tarp", "2-person tarp", 280.g
|
8
|
+
|
9
|
+
# Item quantity can be specified. Here, the weight is per
|
10
|
+
# item, not the total weight of all items.
|
11
|
+
# Here, we specify 8 tent stakes, at 14 grams each.
|
12
|
+
item "Tent stakes", "Aluminum Y-stakes", 14.g, 8
|
13
|
+
|
14
|
+
# Other units of weight can be used as well.
|
15
|
+
# g - grams
|
16
|
+
# kg - kilograms
|
17
|
+
# oz - ounces
|
18
|
+
# lb - pounds
|
19
|
+
# Weight for this item is 24 ounces (24.oz)
|
20
|
+
item "Sleeping back", "Down 20 degree bag", 24.oz
|
21
|
+
|
22
|
+
# Leaving the heavy 4-season tent behind, so we comment it out
|
23
|
+
# item "Tent", "4-season, 3-person tent", 8.5.lb
|
24
|
+
end
|
25
|
+
|
26
|
+
category "Clothing" do
|
27
|
+
# On a backpacking trip, you're likely to wear some of your
|
28
|
+
# clothes (we hope!), while others, like rain gear and extra socks
|
29
|
+
# will be carried most of the time in your pack.
|
30
|
+
worn "T-shirt", "Wool shirt", 150.g
|
31
|
+
worn "Sock", "Wool socks", 80.g
|
32
|
+
worn "Shorts", "Running shorts", 90.g
|
33
|
+
|
34
|
+
# Some things are better left without a description. Just use an
|
35
|
+
# empty string ("").
|
36
|
+
worn "Underwear", "", 85.g
|
37
|
+
|
38
|
+
# The following items are carried in the pack, not worn.
|
39
|
+
item "Rain Jacket", "Keepin' high and dry!", 200.g
|
40
|
+
item "Insulated Jacket", "Down duffy jacket", 275.g
|
41
|
+
item "Warm hat", "Fleece beanie", 90.g
|
42
|
+
end
|
43
|
+
|
44
|
+
# Group your pack's items into multiple categories.
|
45
|
+
category "Cooking" do
|
46
|
+
item "Stove", "Canister stove", 90.g
|
47
|
+
item "Cooking pot", "900ml Titanium pot", 125.g
|
48
|
+
|
49
|
+
# Items like fuel are consumable. They are included in your
|
50
|
+
# maximum pack weight, but not in your base weight.
|
51
|
+
consumable "Fuel", "All season fuel blend", 110.g
|
52
|
+
|
53
|
+
# The canister the fuel comes in is not consumable; to accurately
|
54
|
+
# calculate your pack weight, you should list it separately.
|
55
|
+
item "Fuel canister", "4 ounce fuel canister, empty weight", 90.g
|
56
|
+
end
|
57
|
+
|
58
|
+
category "Food" do
|
59
|
+
# Food is also consumable, of course. You can use either the
|
60
|
+
# "food" or "consumable" item type; the result is the same.
|
61
|
+
|
62
|
+
# For each meal (breakfast, snacks, dinner), we specify the weight
|
63
|
+
# per day, with a quantity of 7, one for each day.
|
64
|
+
food "Breakfast", "Oatmeal", 150.g, 7
|
65
|
+
food "Snacks", "G.O.R.P", 300.g, 7
|
66
|
+
food "Dinner", "Dehydrated gruel", 180.g, 7
|
67
|
+
end
|
68
|
+
end
|
data/exe/packlist
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'packlist'
|
4
|
+
|
5
|
+
def output_filename(filename)
|
6
|
+
basename = File.basename(filename, ".*")
|
7
|
+
dirname = File.dirname(filename)
|
8
|
+
File.join(dirname, "#{basename}.html")
|
9
|
+
end
|
10
|
+
|
11
|
+
ARGV.each do |file|
|
12
|
+
pack = PackList::DSL.load(file)
|
13
|
+
report = PackList::Report.new pack
|
14
|
+
|
15
|
+
report.save_to(output_filename(file))
|
16
|
+
end
|
data/lib/packlist.rb
ADDED
data/lib/packlist/dsl.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'packlist/model'
|
2
|
+
|
3
|
+
module PackList
|
4
|
+
module DSL
|
5
|
+
def packlist(&block)
|
6
|
+
builder = PackBuilder.new &block
|
7
|
+
builder.build
|
8
|
+
end
|
9
|
+
module_function :packlist
|
10
|
+
|
11
|
+
class PackBuilder
|
12
|
+
def initialize(&block)
|
13
|
+
@categories = []
|
14
|
+
if block_given?
|
15
|
+
instance_eval &block
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def name(name)
|
20
|
+
@name = name
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def description(description)
|
25
|
+
@description = description
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def build
|
30
|
+
PackList.new @name, @description, @categories
|
31
|
+
end
|
32
|
+
|
33
|
+
def category(name, description=nil, &block)
|
34
|
+
cat_builder = CategoryBuilder.new name, description, &block
|
35
|
+
category = cat_builder.build
|
36
|
+
@categories << category
|
37
|
+
category
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
class CategoryBuilder
|
43
|
+
def initialize(name, description, &block)
|
44
|
+
@category = Category.new name, description
|
45
|
+
if block_given?
|
46
|
+
instance_eval(&block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def item(name, description, weight, quantity=1)
|
51
|
+
add_item(name, description, weight, :item, quantity)
|
52
|
+
end
|
53
|
+
|
54
|
+
def worn(name, description, weight, quantity=1)
|
55
|
+
add_item(name, description, weight, :worn, quantity)
|
56
|
+
end
|
57
|
+
|
58
|
+
def consumable(name, description, weight=:oz, quantity=1)
|
59
|
+
add_item(name, description, weight, :consumable, quantity)
|
60
|
+
end
|
61
|
+
|
62
|
+
alias_method :food, :consumable
|
63
|
+
|
64
|
+
def build
|
65
|
+
@category
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def add_item(name, description, weight, type, quantity)
|
71
|
+
item = Item.new(name, description, weight, type, quantity: quantity)
|
72
|
+
@category << item
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.load(filename)
|
78
|
+
instance_eval(File.read(filename, encoding: "UTF-8"), filename)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module PackList
|
4
|
+
module WeightMixin
|
5
|
+
[:oz, :lb, :g, :kg].each do |units|
|
6
|
+
send :define_method, units do
|
7
|
+
return Weight.new self, units
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Weight
|
13
|
+
WEIGHT_CONVERSIONS = {lb: 453.592, oz: 453.592 / 16, kg: 1000, g: 1}
|
14
|
+
attr_reader :quantity, :units
|
15
|
+
|
16
|
+
def initialize(quantity, units)
|
17
|
+
raise ArgumentError, "Invalid units: #{units}" unless WEIGHT_CONVERSIONS.key?(units)
|
18
|
+
@quantity, @units = quantity, units
|
19
|
+
end
|
20
|
+
|
21
|
+
[:oz, :lb, :g, :kg].each do |units|
|
22
|
+
send :define_method, units do
|
23
|
+
to_units units
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def +(w)
|
28
|
+
# avoid changing units when either weight is zero
|
29
|
+
case
|
30
|
+
when w.quantity == 0
|
31
|
+
self
|
32
|
+
when self.quantity == 0
|
33
|
+
w
|
34
|
+
when w.units == @units
|
35
|
+
Weight.new w.quantity + @quantity, @units
|
36
|
+
else
|
37
|
+
self + w.to_units(@units)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def *(x)
|
42
|
+
Weight.new x * @quantity, @units
|
43
|
+
end
|
44
|
+
|
45
|
+
ZERO = Weight.new(0, :g)
|
46
|
+
|
47
|
+
# private
|
48
|
+
def in_units(new_units)
|
49
|
+
quantity * WEIGHT_CONVERSIONS[units] / WEIGHT_CONVERSIONS[new_units]
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_units(new_units)
|
53
|
+
Weight.new in_units(new_units), new_units
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
"#{'%.1f' % quantity} #{units}"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
class Item
|
64
|
+
attr_accessor :name, :description, :weight, :quantity
|
65
|
+
|
66
|
+
def initialize(name, description, weight, type=nil, quantity: 1)
|
67
|
+
@name, @description, @quantity, @type = name, description, quantity, type
|
68
|
+
@weight = weight.respond_to?(:units) ? weight : Weight.new(weight, :oz)
|
69
|
+
end
|
70
|
+
|
71
|
+
def total_weight
|
72
|
+
return @weight * @quantity
|
73
|
+
end
|
74
|
+
|
75
|
+
def worn?
|
76
|
+
return @type == :worn
|
77
|
+
end
|
78
|
+
|
79
|
+
def consumable?
|
80
|
+
return @type == :consumable
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
class Category
|
86
|
+
include Enumerable
|
87
|
+
extend Forwardable
|
88
|
+
|
89
|
+
attr_accessor :name, :description
|
90
|
+
attr_reader :items
|
91
|
+
def_delegators :@items, :each, :<<, :include?, :empty?
|
92
|
+
|
93
|
+
def initialize(name, description=nil)
|
94
|
+
@name, @description = name, description
|
95
|
+
@items = []
|
96
|
+
end
|
97
|
+
|
98
|
+
def total_weight
|
99
|
+
@items.collect {|item| item.total_weight}.reduce(Weight::ZERO, :+)
|
100
|
+
end
|
101
|
+
|
102
|
+
def total_pack_weight
|
103
|
+
base_weight + consumable_weight
|
104
|
+
end
|
105
|
+
|
106
|
+
def worn_weight
|
107
|
+
items = @items.select {|item| item.worn? }
|
108
|
+
items.collect {|item| item.total_weight}.reduce(Weight::ZERO, :+)
|
109
|
+
end
|
110
|
+
|
111
|
+
def consumable_weight
|
112
|
+
items = @items.select {|item| item.consumable? }
|
113
|
+
items.collect {|item| item.total_weight}.reduce(Weight::ZERO, :+)
|
114
|
+
end
|
115
|
+
|
116
|
+
def base_weight
|
117
|
+
items = @items.reject {|item| item.worn? || item.consumable? }
|
118
|
+
items.collect {|item| item.total_weight}.reduce(Weight::ZERO, :+)
|
119
|
+
end
|
120
|
+
|
121
|
+
def total_quantity
|
122
|
+
@items.collect {|item| item.quantity}.reduce(0, :+)
|
123
|
+
end
|
124
|
+
|
125
|
+
def item_count
|
126
|
+
@items.length
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
class PackList
|
132
|
+
include Enumerable
|
133
|
+
extend Forwardable
|
134
|
+
|
135
|
+
attr_accessor :name, :description
|
136
|
+
attr_reader :categories
|
137
|
+
def_delegators :@categories, :each, :<<, :include?, :empty?
|
138
|
+
|
139
|
+
def initialize(name, description=nil, categories=[])
|
140
|
+
@name, @description = name, description
|
141
|
+
|
142
|
+
@categories = categories
|
143
|
+
end
|
144
|
+
|
145
|
+
def total_weight
|
146
|
+
@categories.collect {|category| category.total_weight}.reduce(Weight::ZERO, :+)
|
147
|
+
end
|
148
|
+
|
149
|
+
def total_pack_weight
|
150
|
+
base_weight + consumable_weight
|
151
|
+
end
|
152
|
+
|
153
|
+
def worn_weight
|
154
|
+
@categories.collect {|category| category.worn_weight}.reduce(Weight::ZERO, :+)
|
155
|
+
end
|
156
|
+
|
157
|
+
def consumable_weight
|
158
|
+
@categories.collect {|category| category.consumable_weight}.reduce(Weight::ZERO, :+)
|
159
|
+
end
|
160
|
+
|
161
|
+
def base_weight
|
162
|
+
@categories.collect {|category| category.base_weight}.reduce(Weight::ZERO, :+)
|
163
|
+
end
|
164
|
+
|
165
|
+
def total_quantity
|
166
|
+
@categories.collect {|category| category.total_quantity}.reduce(0, :+)
|
167
|
+
end
|
168
|
+
|
169
|
+
def item_count
|
170
|
+
@categories.collect {|category| category.item_count}.reduce(0, :+)
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class Numeric
|
177
|
+
include PackList::WeightMixin
|
178
|
+
end
|