plansheet 0.17.1 → 0.23.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +5 -6
- data/Guardfile +12 -5
- data/exe/plansheet +26 -0
- data/lib/plansheet/pool.rb +11 -8
- data/lib/plansheet/project/yaml.rb +34 -3
- data/lib/plansheet/project.rb +119 -8
- data/lib/plansheet/sheet.rb +11 -5
- data/lib/plansheet/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e479f020bfa87192523fd231ec0704d07a2e0e92805eaf76720bf3ee62469c4a
|
4
|
+
data.tar.gz: c197a3c2730522f7f70aa4793ff50670e91e04f57f15874bef174c0f94b44f83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77d8895595f834e4bf6f844aabd20178b3f50172071a2713e81f50e9e0e21f4c9cc29df0550cfba085419f7508eeb188417610ce8364b18fdcdd8a6012290eed
|
7
|
+
data.tar.gz: 1da8aab0fe36a1775584afbdd140c3b0e0b6a6ed43202b2987311aa258bc3a2d59ce6109cb88c11c6a8a9335bd5770c9c2e66c1ff3f74c3f26cd4c0c07120733
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
plansheet (0.
|
4
|
+
plansheet (0.23.2)
|
5
5
|
dc-kwalify (~> 1.0)
|
6
6
|
rgl (= 0.5.8)
|
7
7
|
|
@@ -23,10 +23,9 @@ GEM
|
|
23
23
|
pry (>= 0.13.0)
|
24
24
|
shellany (~> 0.0)
|
25
25
|
thor (>= 0.18.1)
|
26
|
-
guard-
|
27
|
-
|
28
|
-
|
29
|
-
minitest (>= 3.0)
|
26
|
+
guard-rake (1.0.0)
|
27
|
+
guard
|
28
|
+
rake
|
30
29
|
lazy_priority_queue (0.1.1)
|
31
30
|
listen (3.7.1)
|
32
31
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
@@ -87,7 +86,7 @@ PLATFORMS
|
|
87
86
|
|
88
87
|
DEPENDENCIES
|
89
88
|
guard (~> 2.18)
|
90
|
-
guard-
|
89
|
+
guard-rake (~> 1.0)
|
91
90
|
minitest (~> 5.0)
|
92
91
|
plansheet!
|
93
92
|
rake (~> 13.0)
|
data/Guardfile
CHANGED
@@ -1,9 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
directories(%w[lib test].select { |d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist") })
|
3
|
+
directories(%w[. exe bin lib test].select { |d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist") })
|
4
4
|
|
5
|
-
guard :
|
6
|
-
watch(
|
7
|
-
watch(
|
8
|
-
watch(
|
5
|
+
guard :rake, task: "default" do
|
6
|
+
watch("Gemfile")
|
7
|
+
watch("Rakefile")
|
8
|
+
watch("Guardfile")
|
9
|
+
watch(%r{^test/test_(.*)\.rb$})
|
10
|
+
watch("exe/plansheet")
|
11
|
+
watch("bin/console")
|
12
|
+
watch(%r{^test/test_(.*)\.rb$})
|
13
|
+
watch(%r{^lib/plansheet/(.*)\.rb$})
|
14
|
+
watch(%r{^lib/plansheet/project/(.*)\.rb$})
|
15
|
+
watch(%r{^lib/plansheet\.rb$})
|
9
16
|
end
|
data/exe/plansheet
CHANGED
@@ -13,10 +13,22 @@ parser.on(
|
|
13
13
|
"--sort",
|
14
14
|
"Sort project files"
|
15
15
|
)
|
16
|
+
parser.on(
|
17
|
+
"--irb",
|
18
|
+
"Open IRB console after loading projects"
|
19
|
+
)
|
16
20
|
parser.on(
|
17
21
|
"--cli",
|
18
22
|
"CLI dump of projects (WIP)"
|
19
23
|
)
|
24
|
+
parser.on(
|
25
|
+
"--stats",
|
26
|
+
"Various stats (WIP)"
|
27
|
+
)
|
28
|
+
parser.on(
|
29
|
+
"--time-roi",
|
30
|
+
"Show projects with a time return-on-investment"
|
31
|
+
)
|
20
32
|
parser.on(
|
21
33
|
"--calendar",
|
22
34
|
"List of projects ordered by due date"
|
@@ -36,9 +48,23 @@ if options[:sheet] || options.empty?
|
|
36
48
|
require "plansheet/sheet"
|
37
49
|
Dir.mkdir config["output_dir"] unless Dir.exist? config["output_dir"]
|
38
50
|
Plansheet::Sheet.new("#{config["output_dir"]}/projects.md", pool.projects)
|
51
|
+
elsif options[:irb]
|
52
|
+
binding.irb # rubocop:disable Lint/Debugger
|
53
|
+
elsif options[:stats]
|
54
|
+
puts "# of projects: #{pool.projects.count}"
|
55
|
+
puts "# of tasks: #{pool.projects.sum { |x| x&.tasks&.count || 0 }}"
|
56
|
+
puts "# of locations: #{pool.projects.collect(&:location).flatten.delete_if(&:nil?).uniq.count}"
|
57
|
+
puts "combined time estimate: #{pool.projects.sum { |x| x.time_estimate_minutes || 0 }} minutes"
|
39
58
|
elsif options[:sort]
|
40
59
|
# Pool sorts projects, this now just matches old behaviour
|
41
60
|
pool.write_projects
|
61
|
+
elsif options[:"time-roi"]
|
62
|
+
project_arr = pool.projects.select {|x| x.time_roi_payoff != 0 && !x.dropped_or_done?}.sort
|
63
|
+
project_arr.each do |proj|
|
64
|
+
puts proj
|
65
|
+
puts "time ROI payoff: #{proj.time_roi_payoff}"
|
66
|
+
puts "\n"
|
67
|
+
end
|
42
68
|
elsif options[:calendar]
|
43
69
|
# TODO: add a project filter method
|
44
70
|
project_arr = pool.projects
|
data/lib/plansheet/pool.rb
CHANGED
@@ -14,10 +14,11 @@ module Plansheet
|
|
14
14
|
priority
|
15
15
|
defer
|
16
16
|
due
|
17
|
+
time_roi
|
17
18
|
status
|
18
19
|
].freeze
|
19
20
|
|
20
|
-
def initialize(config)
|
21
|
+
def initialize(config, debug: false)
|
21
22
|
@projects_dir = config[:projects_dir]
|
22
23
|
@sort_order = config[:sort_order]
|
23
24
|
# @completed_projects_dir = config(:completed_projects_dir)
|
@@ -26,17 +27,19 @@ module Plansheet
|
|
26
27
|
# until runtime. I'm sure this design decision definitely won't bite me
|
27
28
|
# in the future ;-) Fortunately, it's also not a problem that can't be
|
28
29
|
# walked back from.
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
# rubocop:disable Lint/OrAssignmentToConstant
|
31
|
+
Plansheet::Pool::POOL_COMPARISON_ORDER ||= config[:sort_order] if config[:sort_order]
|
32
|
+
puts "using config sort order" if config[:sort_order]
|
33
|
+
Plansheet::Pool::POOL_COMPARISON_ORDER ||= Plansheet::Pool::DEFAULT_COMPARISON_ORDER
|
34
|
+
# rubocop:enable Lint/OrAssignmentToConstant
|
34
35
|
require_relative "project"
|
35
|
-
|
36
|
-
|
36
|
+
|
37
|
+
load_projects_dir(@projects_dir) unless debug
|
38
|
+
sort_projects if @projects
|
37
39
|
end
|
38
40
|
|
39
41
|
def sort_projects
|
42
|
+
@projects ||= []
|
40
43
|
@projects.sort!
|
41
44
|
# lookup_hash returns the index of a project
|
42
45
|
lookup_hash = Hash.new nil
|
@@ -9,6 +9,8 @@ require "kwalify"
|
|
9
9
|
module Plansheet
|
10
10
|
# Once there's some stability in plansheet and dc-kwalify, will pre-load this
|
11
11
|
# to save the later YAML.load
|
12
|
+
YAML_TIME_REGEX = "/\\d+[mh] ?\d*m?/" # TODO: regex is bad, adjust for better handling of pretty time
|
13
|
+
YAML_DATE_REGEX = "/\\d+[dw]/"
|
12
14
|
PROJECT_YAML_SCHEMA = <<~YAML
|
13
15
|
desc: dc-tasks project schema
|
14
16
|
type: seq
|
@@ -18,6 +20,7 @@ module Plansheet
|
|
18
20
|
"project":
|
19
21
|
desc: Project name
|
20
22
|
type: str
|
23
|
+
unique: true
|
21
24
|
"namespace":
|
22
25
|
desc: Project name
|
23
26
|
type: str
|
@@ -51,14 +54,42 @@ module Plansheet
|
|
51
54
|
"time_estimate":
|
52
55
|
desc: The estimated amount of time before a project is completed
|
53
56
|
type: str
|
57
|
+
pattern: #{YAML_TIME_REGEX}
|
58
|
+
"daily_time_roi":
|
59
|
+
desc: The estimated amount of time saved daily by completing this project
|
60
|
+
type: str
|
61
|
+
pattern: #{YAML_TIME_REGEX}
|
62
|
+
"weekly_time_roi":
|
63
|
+
desc: The estimated amount of time saved daily by completing this project
|
64
|
+
type: str
|
65
|
+
pattern: #{YAML_TIME_REGEX}
|
66
|
+
"yearly_time_roi":
|
67
|
+
desc: The estimated amount of time saved daily by completing this project
|
68
|
+
type: str
|
69
|
+
pattern: #{YAML_TIME_REGEX}
|
70
|
+
"day_of_week":
|
71
|
+
desc: recurring day of week project
|
72
|
+
type: str
|
73
|
+
enum:
|
74
|
+
- Sunday
|
75
|
+
- Monday
|
76
|
+
- Tuesday
|
77
|
+
- Wednesday
|
78
|
+
- Thursday
|
79
|
+
- Friday
|
80
|
+
- Saturday
|
54
81
|
"frequency":
|
55
|
-
desc: The amount of time before a recurring project moves to ready status again from when it was last done (
|
82
|
+
desc: The amount of time before a recurring project moves to ready status again from when it was last done, with a set due date (eg. a bill becomes due)
|
83
|
+
type: str
|
84
|
+
pattern: #{YAML_DATE_REGEX}
|
85
|
+
"last_for":
|
86
|
+
desc: "The amount of time before a recurring project moves to ready status again from when it was last done, with a set defer date (eg. inflating a bike tire). If your project 'can't wait' a day or two, you should use frequency."
|
56
87
|
type: str
|
57
|
-
pattern:
|
88
|
+
pattern: #{YAML_DATE_REGEX}
|
58
89
|
"lead_time":
|
59
90
|
desc: The amount of time before a recurring project is "due" moved to ready where the project (sort of a deferral mechanism) (WIP)
|
60
91
|
type: str
|
61
|
-
pattern:
|
92
|
+
pattern: #{YAML_DATE_REGEX}
|
62
93
|
"due":
|
63
94
|
desc: Due date of the task
|
64
95
|
type: date
|
data/lib/plansheet/project.rb
CHANGED
@@ -5,6 +5,13 @@ require "date"
|
|
5
5
|
require_relative "project/yaml"
|
6
6
|
require_relative "project/stringify"
|
7
7
|
|
8
|
+
# Needed for Project#time_estimate, would be much happier *not* patching Array
|
9
|
+
class Array
|
10
|
+
def nil_if_empty
|
11
|
+
count.zero? ? nil : self
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
8
15
|
module Plansheet
|
9
16
|
PROJECT_STATUS_PRIORITY = {
|
10
17
|
"wip" => 1,
|
@@ -17,6 +24,12 @@ module Plansheet
|
|
17
24
|
"done" => 8
|
18
25
|
}.freeze
|
19
26
|
|
27
|
+
# Pre-compute the next days-of-week
|
28
|
+
NEXT_DOW = 0.upto(6).to_h do |x|
|
29
|
+
d = Date.today + x
|
30
|
+
[d.strftime("%A"), d]
|
31
|
+
end.freeze
|
32
|
+
|
20
33
|
def self.parse_date_duration(str)
|
21
34
|
return Regexp.last_match(1).to_i if str.strip.match(/(\d+)[dD]/)
|
22
35
|
return (Regexp.last_match(1).to_i * 7) if str.strip.match(/(\d+)[wW]/)
|
@@ -24,6 +37,29 @@ module Plansheet
|
|
24
37
|
raise "Can't parse time duration string #{str}"
|
25
38
|
end
|
26
39
|
|
40
|
+
def self.parse_time_duration(str)
|
41
|
+
if str.match(/(\d+h) (\d+m)/)
|
42
|
+
return (parse_time_duration(Regexp.last_match(1)) + parse_time_duration(Regexp.last_match(2)))
|
43
|
+
end
|
44
|
+
|
45
|
+
return Regexp.last_match(1).to_i if str.strip.match(/(\d+)m/)
|
46
|
+
return (Regexp.last_match(1).to_f * 60).to_i if str.strip.match(/(\d+\.?\d*)h/)
|
47
|
+
|
48
|
+
raise "Can't parse time duration string #{str}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.build_time_duration(minutes)
|
52
|
+
if minutes > 59
|
53
|
+
if (minutes % 60).zero?
|
54
|
+
"#{minutes / 60}h"
|
55
|
+
else
|
56
|
+
"#{minutes / 60}h #{minutes % 60}m"
|
57
|
+
end
|
58
|
+
else
|
59
|
+
"#{minutes}m"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
27
63
|
# The use of instance_variable_set/get probably seems a bit weird, but the
|
28
64
|
# intent is to avoid object allocation on non-existent project properties, as
|
29
65
|
# well as avoiding a bunch of copy-paste boilerplate when adding a new
|
@@ -33,6 +69,9 @@ module Plansheet
|
|
33
69
|
class Project
|
34
70
|
include Comparable
|
35
71
|
|
72
|
+
TIME_EST_REGEX = /\((\d+\.?\d*[mMhH])\)$/.freeze
|
73
|
+
TIME_EST_REGEX_NO_CAPTURE = /\(\d+\.?\d*[mMhH]\)$/.freeze
|
74
|
+
|
36
75
|
PROJECT_PRIORITY = {
|
37
76
|
"high" => 1,
|
38
77
|
"medium" => 2,
|
@@ -42,13 +81,14 @@ module Plansheet
|
|
42
81
|
COMPARISON_ORDER_SYMS = Plansheet::Pool::POOL_COMPARISON_ORDER.map { |x| "compare_#{x}".to_sym }.freeze
|
43
82
|
# NOTE: The order of these affects presentation!
|
44
83
|
# namespace is derived from file name
|
45
|
-
STRING_PROPERTIES = %w[priority status location notes time_estimate
|
84
|
+
STRING_PROPERTIES = %w[priority status location notes time_estimate daily_time_roi weekly_time_roi yearly_time_roi
|
85
|
+
day_of_week frequency last_for lead_time].freeze
|
46
86
|
DATE_PROPERTIES = %w[due defer completed_on created_on starts_on last_done last_reviewed].freeze
|
47
87
|
ARRAY_PROPERTIES = %w[dependencies externals urls tasks done tags].freeze
|
48
88
|
|
49
89
|
ALL_PROPERTIES = STRING_PROPERTIES + DATE_PROPERTIES + ARRAY_PROPERTIES
|
50
90
|
|
51
|
-
attr_reader :name, :priority_val, *ALL_PROPERTIES
|
91
|
+
attr_reader :name, :priority_val, :time_estimate_minutes, *ALL_PROPERTIES
|
52
92
|
attr_accessor :namespace
|
53
93
|
|
54
94
|
def initialize(options)
|
@@ -74,6 +114,47 @@ module Plansheet
|
|
74
114
|
else
|
75
115
|
PROJECT_PRIORITY["low"]
|
76
116
|
end
|
117
|
+
|
118
|
+
# Remove stale defer dates
|
119
|
+
remove_instance_variable("@defer") if @defer && (@defer < Date.today)
|
120
|
+
|
121
|
+
# Add a created_on field if it doesn't exist
|
122
|
+
instance_variable_set("@created_on", Date.today) unless @created_on
|
123
|
+
|
124
|
+
# Handle nil-value tasks
|
125
|
+
if @tasks
|
126
|
+
@tasks.compact!
|
127
|
+
remove_instance_variable("@tasks") if @tasks.empty?
|
128
|
+
end
|
129
|
+
|
130
|
+
# Generate time estimate from tasks if specified
|
131
|
+
# Stomps time_estimate field
|
132
|
+
if @tasks
|
133
|
+
@time_estimate_minutes = @tasks&.select do |t|
|
134
|
+
t.match? TIME_EST_REGEX_NO_CAPTURE
|
135
|
+
end&.nil_if_empty&.map { |t| Plansheet::Project.task_time_estimate(t) }&.sum
|
136
|
+
elsif @time_estimate
|
137
|
+
# No tasks with estimates, but there's an explicit time_estimate
|
138
|
+
# Convert the field to minutes
|
139
|
+
@time_estimate_minutes = Plansheet.parse_time_duration(@time_estimate)
|
140
|
+
end
|
141
|
+
if @time_estimate_minutes # rubocop:disable Style/GuardClause
|
142
|
+
# Rewrite time_estimate field
|
143
|
+
@time_estimate = Plansheet.build_time_duration(@time_estimate_minutes)
|
144
|
+
|
145
|
+
yms = yearly_minutes_saved
|
146
|
+
@time_roi_payoff = yms.to_f / @time_estimate_minutes if yms
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def yearly_minutes_saved
|
151
|
+
if @daily_time_roi
|
152
|
+
Plansheet.parse_time_duration(@daily_time_roi) * 365
|
153
|
+
elsif @weekly_time_roi
|
154
|
+
Plansheet.parse_time_duration(@weekly_time_roi) * 52
|
155
|
+
elsif @yearly_time_roi
|
156
|
+
Plansheet.parse_time_duration(@yearly_time_roi)
|
157
|
+
end
|
77
158
|
end
|
78
159
|
|
79
160
|
def <=>(other)
|
@@ -89,6 +170,14 @@ module Plansheet
|
|
89
170
|
priority_val <=> other.priority_val
|
90
171
|
end
|
91
172
|
|
173
|
+
def time_roi_payoff
|
174
|
+
@time_roi_payoff || 0
|
175
|
+
end
|
176
|
+
|
177
|
+
def compare_time_roi(other)
|
178
|
+
other.time_roi_payoff <=> time_roi_payoff
|
179
|
+
end
|
180
|
+
|
92
181
|
def compare_status(other)
|
93
182
|
PROJECT_STATUS_PRIORITY[status] <=> PROJECT_STATUS_PRIORITY[other.status]
|
94
183
|
end
|
@@ -178,7 +267,8 @@ module Plansheet
|
|
178
267
|
|
179
268
|
def subsequent_recurring_status
|
180
269
|
return "done" if @lead_time && defer > Date.today
|
181
|
-
return "done" if
|
270
|
+
return "done" if @last_for && defer > Date.today
|
271
|
+
return "done" if due && due > Date.today
|
182
272
|
|
183
273
|
task_based_status
|
184
274
|
end
|
@@ -191,39 +281,60 @@ module Plansheet
|
|
191
281
|
# Due date either explicit or recurring
|
192
282
|
def due
|
193
283
|
return @due if @due
|
194
|
-
return recurring_due_date if
|
284
|
+
return recurring_due_date if recurring_due?
|
195
285
|
|
196
286
|
nil
|
197
287
|
end
|
198
288
|
|
199
289
|
def recurring_due_date
|
200
290
|
if @last_done
|
201
|
-
@last_done + Plansheet.parse_date_duration(@frequency)
|
202
|
-
|
203
|
-
|
291
|
+
return @last_done + Plansheet.parse_date_duration(@frequency) if @frequency
|
292
|
+
|
293
|
+
if @day_of_week
|
294
|
+
return Date.today + 7 if @last_done == Date.today
|
295
|
+
return @last_done + 7 if @last_done < Date.today - 7
|
296
|
+
|
297
|
+
return NEXT_DOW[@day_of_week]
|
298
|
+
end
|
204
299
|
end
|
300
|
+
|
301
|
+
# Going to assume this is the first time, so due today
|
302
|
+
Date.today
|
205
303
|
end
|
206
304
|
|
207
305
|
def defer
|
208
306
|
return @defer if @defer
|
209
307
|
return lead_time_deferral if @lead_time && due
|
308
|
+
return last_for_deferral if @last_for
|
210
309
|
|
211
310
|
nil
|
212
311
|
end
|
213
312
|
|
313
|
+
def last_for_deferral
|
314
|
+
return @last_done + Plansheet.parse_date_duration(@last_for) if @last_done
|
315
|
+
end
|
316
|
+
|
214
317
|
def lead_time_deferral
|
215
318
|
[(due - Plansheet.parse_date_duration(@lead_time)),
|
216
319
|
Date.today].max
|
217
320
|
end
|
218
321
|
|
322
|
+
def recurring_due?
|
323
|
+
!@frequency.nil? || !@day_of_week.nil?
|
324
|
+
end
|
325
|
+
|
219
326
|
def recurring?
|
220
|
-
!@frequency.nil?
|
327
|
+
!@frequency.nil? || !@day_of_week.nil? || !@last_done.nil?
|
221
328
|
end
|
222
329
|
|
223
330
|
def dropped_or_done?
|
224
331
|
status == "dropped" || status == "done"
|
225
332
|
end
|
226
333
|
|
334
|
+
def self.task_time_estimate(str)
|
335
|
+
Plansheet.parse_time_duration(Regexp.last_match(1)) if str.match(TIME_EST_REGEX)
|
336
|
+
end
|
337
|
+
|
227
338
|
def to_h
|
228
339
|
h = { "project" => @name, "namespace" => @namespace }
|
229
340
|
ALL_PROPERTIES.each do |prop|
|
data/lib/plansheet/sheet.rb
CHANGED
@@ -28,20 +28,26 @@ module Plansheet
|
|
28
28
|
|
29
29
|
def project_minipage(proj)
|
30
30
|
str = String.new
|
31
|
-
str << "\\begin{minipage}{
|
31
|
+
str << "\\begin{minipage}{6cm}\n"
|
32
32
|
str << project_header(proj)
|
33
33
|
proj&.tasks&.each do |t|
|
34
|
-
str << "$\\square$ #{t} \\\\\n"
|
34
|
+
str << "$\\square$ #{sanitize_string(t)} \\\\\n"
|
35
35
|
end
|
36
36
|
str << "\\end{minipage}\n"
|
37
37
|
str
|
38
38
|
end
|
39
39
|
|
40
|
+
def sanitize_string(str)
|
41
|
+
str.gsub("_", '\_')
|
42
|
+
end
|
43
|
+
|
40
44
|
def project_header(proj)
|
41
45
|
str = String.new
|
42
|
-
str << "#{proj.namespace}: #{proj.name}
|
43
|
-
str <<
|
44
|
-
str << " -
|
46
|
+
str << "#{sanitize_string(proj.namespace)}: #{sanitize_string(proj.name)}\\\\\n"
|
47
|
+
str << proj.status.to_s
|
48
|
+
str << " - #{sanitize_string(proj.location)}" if proj.location
|
49
|
+
str << " due: #{proj.due}" if proj.due
|
50
|
+
str << " time: #{proj.time_estimate}" if proj.time_estimate
|
45
51
|
str << " \\\\\n"
|
46
52
|
str
|
47
53
|
end
|
data/lib/plansheet/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: plansheet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.23.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Crosby
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-07-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dc-kwalify
|