plansheet 0.7.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
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