plansheet 0.17.1 → 0.23.2
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 +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
|