plansheet 0.6.1 → 0.9.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: 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