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.
@@ -0,0 +1,2 @@
1
+ @import "system-font";
2
+ @import "report";
@@ -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
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
Binary file
@@ -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
@@ -0,0 +1,4 @@
1
+ require 'packlist/version'
2
+ require 'packlist/model'
3
+ require 'packlist/dsl'
4
+ require 'packlist/report'
@@ -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