plansheet 0.7.0 → 0.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98932ed8f5a5d06da4b2f6135d1321e5376a1af2f837d4a65b9f806c7f06b8d9
4
- data.tar.gz: 0ef6f026f7bcbc4712d008ca2843a0b5a9d1715038b2fc91534a8c9951697ec6
3
+ metadata.gz: d1c4e7e5e45d00a1218ac7346c60ba9182c7b73ac2761958c24ea8c8dcf15d9d
4
+ data.tar.gz: 50cc492240c458e5bc13539f03203007b8922dcd2e00e5108d6f929c81419f2e
5
5
  SHA512:
6
- metadata.gz: 95217512c48168c26ef2c5f937dd8b94530bccea1149c2e90df1db039243b9ffb4582efe26ea14ae6393845daa4d8f5a4fec36cff82231d7b6ab6fc6a1255ff6
7
- data.tar.gz: '095dcda8264517252297a7aba139f8c51bddfbe04880acb9a16d2eb3f4a479003e1ee453902acf65fde2ad89720b522b6d6bd118e3f1f3202f2aab9f9ec50d04'
6
+ metadata.gz: 2fe3310c09dfe7d4e79172a476f3bcfc0a3ef6261757a4070a6f49d9857673524663ee97622db603acae911201d5f1acc9cdb65d7c30547ea0e3f4610dc5e0bf
7
+ data.tar.gz: 58aee2a5ab4f95c114461a8ef614823b5c4e22e0961c792623d0720b033a40c1abbdf1d23887b7b5a38b4823138994116d3eb6b42f2675263d0825d567fee173
data/.rubocop_todo.yml CHANGED
@@ -1,26 +1,35 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2022-06-02 13:34:43 UTC using RuboCop version 1.29.1.
3
+ # on 2022-06-04 17:25:54 UTC using RuboCop version 1.29.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
+ # Offense count: 1
10
+ # Configuration parameters: CountComments, CountAsOne.
11
+ Metrics/ClassLength:
12
+ Enabled: false
13
+
9
14
  # Offense count: 1
10
15
  # Configuration parameters: IgnoredMethods.
11
16
  Metrics/CyclomaticComplexity:
12
- Max: 8
17
+ Enabled: false
18
+
19
+ # Offense count: 2
20
+ # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
21
+ Metrics/MethodLength:
22
+ Enabled: false
13
23
 
14
24
  # Offense count: 1
15
25
  # Configuration parameters: IgnoredMethods.
16
26
  Metrics/PerceivedComplexity:
17
- Max: 10
27
+ Enabled: false
18
28
 
19
29
  # Offense count: 2
20
30
  # Configuration parameters: AllowedConstants.
21
31
  Style/Documentation:
22
32
  Exclude:
23
- - 'spec/**/*'
24
33
  - 'test/**/*'
25
34
  - 'lib/plansheet.rb'
26
35
  - 'lib/plansheet/project.rb'
data/Gemfile CHANGED
@@ -6,7 +6,12 @@ source "https://rubygems.org"
6
6
  gemspec
7
7
 
8
8
  group :development, optional: true do
9
+ gem "guard", "~> 2.18"
10
+ gem "guard-minitest", "~> 2.4"
9
11
  gem "minitest", "~> 5.0"
10
12
  gem "rake", "~> 13.0"
13
+
11
14
  gem "rubocop", "~> 1.21"
15
+ gem "rubocop-minitest"
16
+ gem "rubocop-rake"
12
17
  end
data/Gemfile.lock CHANGED
@@ -1,20 +1,51 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- plansheet (0.7.0)
4
+ plansheet (0.12.0)
5
5
  dc-kwalify (~> 1.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
10
  ast (2.4.2)
11
+ coderay (1.1.3)
11
12
  dc-kwalify (1.0.0)
13
+ ffi (1.15.5)
14
+ formatador (1.1.0)
15
+ guard (2.18.0)
16
+ formatador (>= 0.2.4)
17
+ listen (>= 2.7, < 4.0)
18
+ lumberjack (>= 1.0.12, < 2.0)
19
+ nenv (~> 0.1)
20
+ notiffany (~> 0.0)
21
+ pry (>= 0.13.0)
22
+ shellany (~> 0.0)
23
+ thor (>= 0.18.1)
24
+ guard-compat (1.2.1)
25
+ guard-minitest (2.4.6)
26
+ guard-compat (~> 1.2)
27
+ minitest (>= 3.0)
28
+ listen (3.7.1)
29
+ rb-fsevent (~> 0.10, >= 0.10.3)
30
+ rb-inotify (~> 0.9, >= 0.9.10)
31
+ lumberjack (1.2.8)
32
+ method_source (1.0.0)
12
33
  minitest (5.15.0)
34
+ nenv (0.3.0)
35
+ notiffany (0.1.3)
36
+ nenv (~> 0.1)
37
+ shellany (~> 0.0)
13
38
  parallel (1.22.1)
14
39
  parser (3.1.2.0)
15
40
  ast (~> 2.4.1)
41
+ pry (0.14.1)
42
+ coderay (~> 1.1)
43
+ method_source (~> 1.0)
16
44
  rainbow (3.1.1)
17
45
  rake (13.0.6)
46
+ rb-fsevent (0.11.1)
47
+ rb-inotify (0.10.1)
48
+ ffi (~> 1.0)
18
49
  regexp_parser (2.4.0)
19
50
  rexml (3.2.5)
20
51
  rubocop (1.29.1)
@@ -28,17 +59,27 @@ GEM
28
59
  unicode-display_width (>= 1.4.0, < 3.0)
29
60
  rubocop-ast (1.18.0)
30
61
  parser (>= 3.1.1.0)
62
+ rubocop-minitest (0.20.0)
63
+ rubocop (>= 0.90, < 2.0)
64
+ rubocop-rake (0.6.0)
65
+ rubocop (~> 1.0)
31
66
  ruby-progressbar (1.11.0)
67
+ shellany (0.0.1)
68
+ thor (1.2.1)
32
69
  unicode-display_width (2.1.0)
33
70
 
34
71
  PLATFORMS
35
72
  x86_64-linux
36
73
 
37
74
  DEPENDENCIES
75
+ guard (~> 2.18)
76
+ guard-minitest (~> 2.4)
38
77
  minitest (~> 5.0)
39
78
  plansheet!
40
79
  rake (~> 13.0)
41
80
  rubocop (~> 1.21)
81
+ rubocop-minitest
82
+ rubocop-rake
42
83
 
43
84
  BUNDLED WITH
44
85
  2.3.6
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ directories(%w[lib test].select { |d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist") })
4
+
5
+ guard :minitest do
6
+ watch(%r{^test/test_(.*)\.rb$}) { "test" }
7
+ watch(%r{^lib/plansheet/(.*)\.rb$}) { "test" }
8
+ watch(%r{^lib/plansheet\.rb$}) { "test" }
9
+ end
data/exe/plansheet CHANGED
@@ -17,6 +17,10 @@ parser.on(
17
17
  "--cli",
18
18
  "CLI dump of projects (WIP)"
19
19
  )
20
+ parser.on(
21
+ "--location_filter LOCATION",
22
+ "location filter for CLI dump (WIP)"
23
+ )
20
24
  options = {}
21
25
  parser.parse!(into: options)
22
26
 
@@ -32,7 +36,10 @@ elsif options[:sort]
32
36
  Plansheet.resort_projects_in_dir config["projects_dir"]
33
37
  elsif options[:cli]
34
38
  project_arr = Plansheet.load_projects_dir config["projects_dir"]
35
- project_arr.sort.each do |proj|
39
+ project_arr.sort!
40
+ project_arr.delete_if { |x| x.status == "dropped" || x.status == "done" }
41
+ project_arr.select! { |x| x.location == options[:location_filter] } if options[:location_filter]
42
+ project_arr.each do |proj|
36
43
  puts proj
37
44
  puts "\n"
38
45
  end
@@ -1,16 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "yaml"
4
+ require "date"
4
5
 
5
6
  module Plansheet
6
7
  PROJECT_STATUS_PRIORITY = {
7
8
  "wip" => 1,
8
9
  "ready" => 2,
9
10
  "blocked" => 3,
10
- "planning" => 4,
11
- "idea" => 5,
12
- "dropped" => 6,
13
- "done" => 7
11
+ "waiting" => 4,
12
+ "planning" => 5,
13
+ "idea" => 6,
14
+ "dropped" => 7,
15
+ "done" => 8
14
16
  }.freeze
15
17
 
16
18
  PROJECT_PRIORITY = {
@@ -43,9 +45,10 @@ module Plansheet
43
45
  type: str
44
46
  enum:
45
47
  - wip # project is a work-in-progress
46
- - ready # project is fully scoped, ready to go
47
- - blocked # project is blocked, but otherwise ready/wip
48
- - planning # project in planning phase
48
+ - ready # project has tasks, ready to go
49
+ - waiting # project in waiting on some external person/event
50
+ - blocked # project is blocked by another project, but otherwise ready/wip
51
+ - planning # project in planning phase (set manually)
49
52
  - idea # project is little more than an idea
50
53
  - dropped # project has been explicitly dropped, but
51
54
  # want to keep around for reference, etc
@@ -57,6 +60,40 @@ module Plansheet
57
60
  "notes":
58
61
  desc: Free-form notes string
59
62
  type: str
63
+ "time_estimate":
64
+ desc: The estimated amount of time before a project is completed
65
+ type: str
66
+ "frequency":
67
+ desc: The amount of time before a recurring project moves to ready status again from when it was last done (WIP)
68
+ type: str
69
+ pattern: /\\d+[dwDW]/
70
+ "lead_time":
71
+ desc: The amount of time before a recurring project is "due" moved to ready where the project (sort of a deferral mechanism) (WIP)
72
+ type: str
73
+ pattern: /\\d+[dwDW]/
74
+ "due":
75
+ desc: Due date of the task
76
+ type: date
77
+ "defer":
78
+ desc: Defer task until this day
79
+ type: date
80
+ "created_on":
81
+ desc: When the project was created
82
+ type: date
83
+ "starts_on":
84
+ desc: For ICS (WIP)
85
+ type: date
86
+ "last_reviewed":
87
+ desc: When the project was last reviewed (WIP)
88
+ type: date
89
+ "last_done":
90
+ desc: When the recurring project was last completed (WIP)
91
+ type: date
92
+ "dependencies":
93
+ desc: The names of projects that need to be completed before this project can be started/completed
94
+ type: seq
95
+ sequence:
96
+ - type: str
60
97
  "externals":
61
98
  desc: List of external commitments, ie who else cares about project completion?
62
99
  type: seq
@@ -77,9 +114,21 @@ module Plansheet
77
114
  type: seq
78
115
  sequence:
79
116
  - type: str
117
+ "tags":
118
+ desc: List of tags (WIP)
119
+ type: seq
120
+ sequence:
121
+ - type: str
80
122
  YAML
81
123
  PROJECT_SCHEMA = YAML.safe_load(PROJECT_YAML_SCHEMA)
82
124
 
125
+ def self.parse_date_duration(str)
126
+ return Regexp.last_match(1).to_i if str.strip.match(/(\d+)[dD]/)
127
+ return (Regexp.last_match(1).to_i * 7) if str.strip.match(/(\d+)[wW]/)
128
+
129
+ raise "Can't parse time duration string #{str}"
130
+ end
131
+
83
132
  # The use of instance_variable_set/get probably seems a bit weird, but the
84
133
  # intent is to avoid object allocation on non-existent project properties, as
85
134
  # well as avoiding a bunch of copy-paste boilerplate when adding a new
@@ -89,11 +138,20 @@ module Plansheet
89
138
  class Project
90
139
  include Comparable
91
140
 
141
+ DEFAULT_COMPARISON_ORDER = %w[
142
+ completeness
143
+ dependency
144
+ priority
145
+ defer
146
+ due
147
+ status
148
+ ].map { |x| "compare_#{x}".to_sym }.freeze
92
149
  # NOTE: The order of these affects presentation!
93
- STRING_PROPERTIES = %w[priority status location notes].freeze
94
- ARRAY_PROPERTIES = %w[externals urls tasks done].freeze
150
+ STRING_PROPERTIES = %w[priority status location notes time_estimate frequency lead_time].freeze
151
+ DATE_PROPERTIES = %w[due defer created_on starts_on last_done last_reviewed].freeze
152
+ ARRAY_PROPERTIES = %w[dependencies externals urls tasks done tags].freeze
95
153
 
96
- ALL_PROPERTIES = STRING_PROPERTIES + ARRAY_PROPERTIES
154
+ ALL_PROPERTIES = STRING_PROPERTIES + DATE_PROPERTIES + ARRAY_PROPERTIES
97
155
 
98
156
  attr_reader :name, *ALL_PROPERTIES
99
157
 
@@ -118,34 +176,153 @@ module Plansheet
118
176
  end
119
177
 
120
178
  def <=>(other)
121
- if @priority == other.priority
122
- # TODO: if planning status, then sort based on tasks? category? alphabetically?
123
- PROJECT_STATUS_PRIORITY[status] <=> PROJECT_STATUS_PRIORITY[other.status]
124
- else
125
- PROJECT_PRIORITY[@priority] <=> PROJECT_PRIORITY[other.priority]
179
+ ret_val = 0
180
+ DEFAULT_COMPARISON_ORDER.each do |method|
181
+ ret_val = send(method, other)
182
+ break if ret_val != 0
126
183
  end
184
+ ret_val
185
+ end
186
+
187
+ def compare_priority(other)
188
+ PROJECT_PRIORITY[@priority] <=> PROJECT_PRIORITY[other.priority]
189
+ end
190
+
191
+ def compare_status(other)
192
+ PROJECT_STATUS_PRIORITY[status] <=> PROJECT_STATUS_PRIORITY[other.status]
193
+ end
194
+
195
+ def compare_due(other)
196
+ # -1 is receiving object being older
197
+
198
+ # Handle nil
199
+ if @due.nil?
200
+ return 0 if other.due.nil?
201
+
202
+ return 1
203
+ elsif other.due.nil?
204
+ return -1
205
+ end
206
+
207
+ @due <=> other.due
208
+ end
209
+
210
+ def compare_defer(other)
211
+ receiver = @defer.nil? || @defer < Date.today ? Date.today : @defer
212
+ comparison = other.defer.nil? || other.defer < Date.today ? Date.today : other.defer
213
+ receiver <=> comparison
214
+ end
215
+
216
+ def compare_dependency(other)
217
+ return 0 if @dependencies.nil? && other.dependencies.nil?
218
+
219
+ if @dependencies.nil?
220
+ return -1 if other.dependencies.any? do |dep|
221
+ @name.downcase == dep.downcase
222
+ end
223
+ elsif @dependencies.any? do |dep|
224
+ other.name.downcase == dep.downcase
225
+ end
226
+ return 1
227
+ end
228
+ 0
229
+ end
230
+
231
+ # Projects that are dropped or done are considered "complete", insofar as
232
+ # they are only kept around for later reference.
233
+ def compare_completeness(other)
234
+ return 0 if dropped_or_done? && other.dropped_or_done?
235
+ return 0 if !dropped_or_done? && !other.dropped_or_done?
236
+
237
+ dropped_or_done? ? 1 : -1
127
238
  end
128
239
 
129
240
  def status
130
241
  return @status if @status
242
+ return recurring_status if recurring?
243
+ return task_based_status if (@tasks || @done)
244
+ # TODO: return done if done count is positive...
131
245
 
132
- if @tasks&.count&.positive?
133
- if @done&.count&.positive?
134
- "wip"
135
- else
136
- "planning"
137
- end
246
+ "idea"
247
+ end
248
+
249
+ def task_based_status
250
+ if @tasks&.count&.positive? && @done&.count&.positive?
251
+ "wip"
252
+ elsif @tasks&.count&.positive?
253
+ "ready"
254
+ elsif @done&.count&.positive?
255
+ "done"
138
256
  else
139
257
  "idea"
140
258
  end
141
259
  end
142
260
 
261
+ def recurring_status
262
+ # add frequency to last_done
263
+ unless @last_done
264
+ # This recurring project is being done for the first time
265
+ task_based_status
266
+ else
267
+ # This project has been done once before
268
+ subsequent_recurring_status
269
+ end
270
+ end
271
+
272
+ def subsequent_recurring_status
273
+ return "done" if @lead_time && defer > Date.today
274
+ return "done" if due > Date.today
275
+ task_based_status
276
+ end
277
+
278
+ def process_recurring
279
+ # TODO: Tasks will be moved from done->tasks if recurring project is
280
+ # starting again
281
+ end
282
+
283
+ # Due date either explicit or recurring
284
+ def due
285
+ return @due if @due
286
+ return recurring_due_date if recurring?
287
+ nil
288
+ end
289
+
290
+ def recurring_due_date
291
+ if @last_done
292
+ @last_done + Plansheet.parse_date_duration(@frequency)
293
+ else
294
+ Date.today
295
+ end
296
+ end
297
+
298
+ def defer
299
+ return @defer if @defer
300
+ return lead_time_deferral if @lead_time && due
301
+ nil
302
+ end
303
+
304
+ def lead_time_deferral
305
+ [(due - Plansheet.parse_date_duration(@lead_time)),
306
+ Date.today].max
307
+ end
308
+
309
+ def recurring?
310
+ !@frequency.nil?
311
+ end
312
+
313
+ def dropped_or_done?
314
+ status == "dropped" || status == "done"
315
+ end
316
+
143
317
  def to_s
144
318
  str = String.new
145
319
  str << "# #{@name}\n"
146
320
  STRING_PROPERTIES.each do |o|
147
321
  str << stringify_string_property(o)
148
322
  end
323
+ DATE_PROPERTIES.each do |o|
324
+ str << stringify_string_property(o)
325
+ end
149
326
  ARRAY_PROPERTIES.each do |o|
150
327
  str << stringify_array_property(o)
151
328
  end
@@ -160,6 +337,14 @@ module Plansheet
160
337
  end
161
338
  end
162
339
 
340
+ def stringify_date_property(prop)
341
+ if instance_variable_defined? "@#{prop}"
342
+ "#{prop}: #{instance_variable_get("@#{prop}")}\n"
343
+ else
344
+ ""
345
+ end
346
+ end
347
+
163
348
  def stringify_array_property(prop)
164
349
  str = String.new
165
350
  if instance_variable_defined? "@#{prop}"
@@ -188,7 +373,14 @@ module Plansheet
188
373
  def initialize(path)
189
374
  @path = path
190
375
  # TODO: this won't GC, inline validation instead?
191
- @raw = YAML.load_file(path)
376
+
377
+ # Handle pre-Ruby 3.1 psych versions (this is brittle)
378
+ @raw = if Psych::VERSION.split(".")[0].to_i >= 4
379
+ YAML.load_file(path, permitted_classes: [Date])
380
+ else
381
+ YAML.load_file(path)
382
+ end
383
+
192
384
  validate_schema
193
385
  @projects = @raw.map { |proj| Project.new proj }
194
386
  end
@@ -10,7 +10,7 @@ module Plansheet
10
10
  projects_str = String.new
11
11
  projects_str << sheet_header
12
12
 
13
- sorted_arr.first(60).each do |p|
13
+ sorted_arr.each do |p|
14
14
  projects_str << project_minipage(p)
15
15
  end
16
16
  puts "Writing to #{output_file}"
@@ -32,7 +32,7 @@ module Plansheet
32
32
  str = String.new
33
33
  str << "\\begin{minipage}{4.5cm}\n"
34
34
  str << project_header(proj)
35
- proj.tasks.each do |t|
35
+ proj&.tasks&.each do |t|
36
36
  str << "$\\square$ #{t} \\\\\n"
37
37
  end
38
38
  str << "\\end{minipage}\n"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Plansheet
4
- VERSION = "0.7.0"
4
+ VERSION = "0.12.0"
5
5
  end
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.7.0
4
+ version: 0.12.0
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-06-02 00:00:00.000000000 Z
11
+ date: 2022-06-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dc-kwalify
@@ -37,6 +37,7 @@ files:
37
37
  - CODE_OF_CONDUCT.md
38
38
  - Gemfile
39
39
  - Gemfile.lock
40
+ - Guardfile
40
41
  - LICENSE.txt
41
42
  - README.md
42
43
  - Rakefile