plansheet 0.6.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36b1a62d85f75f6fe04c0ad37b6d344dc9d9192a02d093283979986f09532935
4
- data.tar.gz: 13a281430dfc803f940d05efa83c68758db7a4452912cfcfd6b5fe9a7247d69c
3
+ metadata.gz: a8132e26b8647ea119299716b5f88674af3e4264614f7b881133eb6cbd32d7db
4
+ data.tar.gz: c04f8bf586f6866acf03e809ab581b8010b6fb64be486791abb5fdd3274ae6e2
5
5
  SHA512:
6
- metadata.gz: fc3b217bf90a0333dc9f09b011c1f09caba8977a0f19bdd0a3695b76c519da54a5f2144897af49ee6e4f726a8abddc35532c51895c1c37a30422ddd39118676b
7
- data.tar.gz: a553efe99e604078e55ae0ea9cfdfa625a026a242ac92025e6600ebc5e04935c528a4d1807ecbe3a2a469ec6c37d3923cf22bad9c6f292d3ab79d1eca868c58b
6
+ metadata.gz: 6426deb869f89a6ff87d5f760acbab8687a542dd3942cf829c536ceb49c3a8268b18ab188d0ad0cb91457fa435963eb7193dd9844a283ccdfb4114e4dece54d8
7
+ data.tar.gz: 4c03feeae9aed94d4e4df6c931c1317039f1100e67d7475cdbf8b67dae14e753899853291ad46f025e198d93def4b9c67c0c12da24d352d8db0880ae5ebb2f99
data/.rubocop.yml CHANGED
@@ -1,3 +1,5 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
1
3
  AllCops:
2
4
  TargetRubyVersion: 2.6
3
5
  NewCops: enable
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,36 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2022-06-04 17:25:54 UTC using RuboCop version 1.29.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ # Configuration parameters: CountComments, CountAsOne.
11
+ Metrics/ClassLength:
12
+ Max: 105
13
+
14
+ # Offense count: 1
15
+ # Configuration parameters: IgnoredMethods.
16
+ Metrics/CyclomaticComplexity:
17
+ Max: 8
18
+
19
+ # Offense count: 2
20
+ # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
21
+ Metrics/MethodLength:
22
+ Max: 12
23
+
24
+ # Offense count: 1
25
+ # Configuration parameters: IgnoredMethods.
26
+ Metrics/PerceivedComplexity:
27
+ Max: 10
28
+
29
+ # Offense count: 2
30
+ # Configuration parameters: AllowedConstants.
31
+ Style/Documentation:
32
+ Exclude:
33
+ - 'spec/**/*'
34
+ - 'test/**/*'
35
+ - 'lib/plansheet.rb'
36
+ - '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.6.1)
4
+ plansheet (0.9.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
@@ -1,6 +1,7 @@
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 = {
@@ -18,11 +19,6 @@ module Plansheet
18
19
  "medium" => 2,
19
20
  "low" => 3
20
21
  }.freeze
21
- PROJECT_PRIORITY_REV = {
22
- 1 => "high",
23
- 2 => "medium",
24
- 3 => "low"
25
- }.freeze
26
22
 
27
23
  # Once there's some stability in plansheet and dc-kwalify, will pre-load this
28
24
  # to save the later YAML.load
@@ -36,6 +32,13 @@ module Plansheet
36
32
  desc: Project name
37
33
  type: str
38
34
  required: yes
35
+ "priority":
36
+ desc: Project priority
37
+ type: str
38
+ enum:
39
+ - high
40
+ - medium
41
+ - low
39
42
  "status":
40
43
  desc: The current status of the project
41
44
  type: str
@@ -49,15 +52,33 @@ module Plansheet
49
52
  # want to keep around for reference, etc
50
53
  - done # project is finished, but want to keep around
51
54
  # for reference, etc.
52
- "priority":
53
- desc: Project priority
54
- type: str
55
- enum:
56
- - high
57
- - low
58
55
  "location":
59
56
  desc: Location
60
57
  type: str
58
+ "notes":
59
+ desc: Free-form notes string
60
+ type: str
61
+ "due":
62
+ desc: Due date of the task
63
+ type: date
64
+ "defer":
65
+ desc: Defer task until this day
66
+ type: date
67
+ "dependencies":
68
+ desc: The names of projects that need to be completed before this project can be started/completed
69
+ type: seq
70
+ sequence:
71
+ - type: str
72
+ "externals":
73
+ desc: List of external commitments, ie who else cares about project completion?
74
+ type: seq
75
+ sequence:
76
+ - type: str
77
+ "urls":
78
+ desc: List of URLs that may be pertinent
79
+ type: seq
80
+ sequence:
81
+ - type: str
61
82
  "tasks":
62
83
  desc: List of tasks to do
63
84
  type: seq
@@ -68,46 +89,119 @@ module Plansheet
68
89
  type: seq
69
90
  sequence:
70
91
  - type: str
71
- "notes":
72
- desc: Free-form notes string
73
- type: str
74
92
  YAML
75
93
  PROJECT_SCHEMA = YAML.safe_load(PROJECT_YAML_SCHEMA)
94
+
95
+ # The use of instance_variable_set/get probably seems a bit weird, but the
96
+ # intent is to avoid object allocation on non-existent project properties, as
97
+ # well as avoiding a bunch of copy-paste boilerplate when adding a new
98
+ # property. I suspect I'm guilty of premature optimization here, but it's
99
+ # easier to do this at the start than untangle that later (ie easier to
100
+ # unwrap the loops if it's not needed.
76
101
  class Project
77
102
  include Comparable
78
- attr_reader :name, :tasks, :done, :notes, :location, :priority
103
+
104
+ # NOTE: The order of these affects presentation!
105
+ STRING_PROPERTIES = %w[priority status location notes].freeze
106
+ DATE_PROPERTIES = %w[due defer].freeze
107
+ ARRAY_PROPERTIES = %w[dependencies externals urls tasks done].freeze
108
+
109
+ ALL_PROPERTIES = STRING_PROPERTIES + DATE_PROPERTIES + ARRAY_PROPERTIES
110
+
111
+ attr_reader :name, *ALL_PROPERTIES
79
112
 
80
113
  def initialize(options)
81
114
  @name = options["project"]
82
115
 
83
- @tasks = options["tasks"] || []
84
- @done = options["done"] || []
116
+ ALL_PROPERTIES.each do |o|
117
+ instance_variable_set("@#{o}", options[o]) if options[o]
118
+ end
85
119
 
86
- @notes = options["notes"] if options["notes"]
87
- @priority = PROJECT_PRIORITY[options["priority"] || "medium"]
88
- @location = options["location"] if options["location"]
89
- @status = options["status"] if options["status"]
120
+ # The "priority" concept feels flawed - it requires *me* to figure out
121
+ # the priority, as opposed to the program understanding the project in
122
+ # relation to other tasks. If I truly understood the priority of all the
123
+ # projects, I wouldn't need a todo list program. The point is to remove
124
+ # the need for willpower/executive function/coffee. The long-term value
125
+ # of this field will diminish as I add more project properties that can
126
+ # automatically hone in on the most important items based on due
127
+ # date/external commits/penalties for project failure, etc
128
+ #
129
+ # Assume all projects are low priority unless stated otherwise.
130
+ @priority ||= "low"
90
131
  end
91
132
 
92
133
  def <=>(other)
93
- if @priority == other.priority
94
- # TODO: if planning status, then sort based on tasks? category? alphabetically?
95
- PROJECT_STATUS_PRIORITY[status] <=> PROJECT_STATUS_PRIORITY[other.status]
134
+ ret_val = 0
135
+ %i[
136
+ compare_completeness
137
+ compare_dependency
138
+ compare_priority
139
+ compare_due
140
+ compare_defer
141
+ compare_status
142
+ ].each do |method|
143
+ ret_val = send(method, other)
144
+ break if ret_val != 0
145
+ end
146
+ ret_val
147
+ end
148
+
149
+ def compare_priority(other)
150
+ PROJECT_PRIORITY[@priority] <=> PROJECT_PRIORITY[other.priority]
151
+ end
152
+
153
+ def compare_status(other)
154
+ PROJECT_STATUS_PRIORITY[status] <=> PROJECT_STATUS_PRIORITY[other.status]
155
+ end
156
+
157
+ def compare_due(other)
158
+ # -1 is receiving object being older
159
+
160
+ # Handle nil
161
+ if @due.nil?
162
+ return 0 if other.due.nil?
163
+
164
+ return 1
165
+ elsif other.due.nil?
166
+ return -1
167
+ end
168
+
169
+ @due <=> other.due
170
+ end
171
+
172
+ def compare_defer(other)
173
+ receiver = @defer.nil? || @defer < Date.today ? Date.today : @defer
174
+ comparison = other.defer.nil? || other.defer < Date.today ? Date.today : other.defer
175
+ receiver <=> comparison
176
+ end
177
+
178
+ def compare_dependency(other)
179
+ return 0 if @dependencies.nil? && other.dependencies.nil?
180
+ if @dependencies.nil?
181
+ return -1 if other.dependencies.any? {|dep|
182
+ @name.downcase == dep.downcase
183
+ }
96
184
  else
97
- @priority <=> other.priority
185
+ return 1 if @dependencies.any? {|dep|
186
+ other.name.downcase == dep.downcase
187
+ }
98
188
  end
189
+ return 0
99
190
  end
100
191
 
101
- # TODO: clean up priority handling
102
- def priority_string
103
- PROJECT_PRIORITY_REV[@priority]
192
+ # Projects that are dropped or done are considered "complete", insofar as
193
+ # they are only kept around for later reference.
194
+ def compare_completeness(other)
195
+ return 0 if self.dropped_or_done? && other.dropped_or_done?
196
+ return 0 if !self.dropped_or_done? && !other.dropped_or_done?
197
+ self.dropped_or_done? ? 1 : -1
104
198
  end
105
199
 
106
200
  def status
107
201
  return @status if @status
108
202
 
109
- if @tasks.count.positive?
110
- if @done.count.positive?
203
+ if @tasks&.count&.positive?
204
+ if @done&.count&.positive?
111
205
  "wip"
112
206
  else
113
207
  "planning"
@@ -117,32 +211,59 @@ module Plansheet
117
211
  end
118
212
  end
119
213
 
214
+ def dropped_or_done?
215
+ status == "dropped" || status == "done"
216
+ end
217
+
120
218
  def to_s
121
219
  str = String.new
122
220
  str << "# #{@name}\n"
123
- str << "priority: #{priority_string}\n"
124
- str << "status: #{status}\n"
125
- str << "notes: #{notes}\n" unless @notes.nil?
126
- str << "location: #{location}\n" unless @location.nil?
127
- str << "tasks:\n" unless @tasks.empty?
128
- @tasks.each do |t|
129
- str << "- #{t}\n"
221
+ STRING_PROPERTIES.each do |o|
222
+ str << stringify_string_property(o)
223
+ end
224
+ DATE_PROPERTIES.each do |o|
225
+ str << stringify_string_property(o)
130
226
  end
131
- str << "done:\n" unless @done.empty?
132
- @done.each do |d|
133
- str << "- #{d}\n"
227
+ ARRAY_PROPERTIES.each do |o|
228
+ str << stringify_array_property(o)
229
+ end
230
+ str
231
+ end
232
+
233
+ def stringify_string_property(prop)
234
+ if instance_variable_defined? "@#{prop}"
235
+ "#{prop}: #{instance_variable_get("@#{prop}")}\n"
236
+ else
237
+ ""
238
+ end
239
+ end
240
+
241
+ def stringify_date_property(prop)
242
+ if instance_variable_defined? "@#{prop}"
243
+ "#{prop}: #{instance_variable_get("@#{prop}")}\n"
244
+ else
245
+ ""
246
+ end
247
+ end
248
+
249
+ def stringify_array_property(prop)
250
+ str = String.new
251
+ if instance_variable_defined? "@#{prop}"
252
+ str << "#{prop}:\n"
253
+ instance_variable_get("@#{prop}").each do |t|
254
+ str << "- #{t}\n"
255
+ end
134
256
  end
135
257
  str
136
258
  end
137
259
 
138
260
  def to_h
139
261
  h = { "project" => @name }
140
- h["priority"] = priority_string unless priority_string == "medium"
141
- h["status"] = status unless status == "idea"
142
- h["notes"] = @notes unless @notes.nil?
143
- h["location"] = @location unless @location.nil?
144
- h["tasks"] = @tasks unless @tasks.empty?
145
- h["done"] = @done unless @done.empty?
262
+ ALL_PROPERTIES.each do |prop|
263
+ h[prop] = instance_variable_get("@#{prop}") if instance_variable_defined?("@#{prop}")
264
+ end
265
+ h.delete "priority" if h.key?("priority") && h["priority"] == "low"
266
+ h.delete "status" if h.key?("status") && h["status"] == "idea"
146
267
  h
147
268
  end
148
269
  end
@@ -153,7 +274,14 @@ module Plansheet
153
274
  def initialize(path)
154
275
  @path = path
155
276
  # TODO: this won't GC, inline validation instead?
156
- @raw = YAML.load_file(path)
277
+
278
+ # Handle pre-Ruby 3.1 psych versions (this is brittle)
279
+ @raw = if Psych::VERSION.split(".")[0].to_i >= 4
280
+ YAML.load_file(path, permitted_classes: [Date])
281
+ else
282
+ YAML.load_file(path)
283
+ end
284
+
157
285
  validate_schema
158
286
  @projects = @raw.map { |proj| Project.new proj }
159
287
  end
@@ -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.6.1"
4
+ VERSION = "0.9.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.6.1
4
+ version: 0.9.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-05-31 00:00:00.000000000 Z
11
+ date: 2022-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dc-kwalify
@@ -33,9 +33,11 @@ extensions: []
33
33
  extra_rdoc_files: []
34
34
  files:
35
35
  - ".rubocop.yml"
36
+ - ".rubocop_todo.yml"
36
37
  - CODE_OF_CONDUCT.md
37
38
  - Gemfile
38
39
  - Gemfile.lock
40
+ - Guardfile
39
41
  - LICENSE.txt
40
42
  - README.md
41
43
  - Rakefile