plansheet 0.27.0 → 0.30.2

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: e590a914b9d074b7ce833d04aac6e5320684c15a398c1c88d5774ae884cad297
4
- data.tar.gz: 7e7d5004b022e6286e0e7792b9392b95c1536c398c7915a2534ead4dbf364080
3
+ metadata.gz: a918dce2e7ab41c30070205e2dcb75679696047b231e0a0999ed0a940fa3aea8
4
+ data.tar.gz: 5cec12572afb320929ff5efd951bf9f31e69aeff53d35e4886ea17d6891ad30c
5
5
  SHA512:
6
- metadata.gz: 4f03c2eaa802dd20a82180ab4a3ad2b23b2a3b69d6e7536f81a42305a3aab486175e18c767a9cb5b919a7c8d5d7e5a22032253e19b95d47d19c64a4583fd150f
7
- data.tar.gz: 6a1923c54d08ba01d33dbfb8d0c76b813fb57b65edba8d79fd002db43b9c63299a301c763acb5084ab5d1e9a773af24be12b15d5f5c2d4fad3b009f820939cfa
6
+ metadata.gz: 30fdf0a0f98d5dd99de508849c9380210d4b8515d8549ccc4cfb60a8da2263fecd9d6b72fcbaf76fd539aeb826a1c69342930a403d6289942c637e1e5f70e54d
7
+ data.tar.gz: e0cf9b8ec2daa63f29b396ca58e1321d4303fc3ce2cbbccaf3a7bc282d33e2756ed55bce9747ff74223ee026130c1c8b69746ff97a38f189bc540e95f73dd49d
data/.rubocop.yml CHANGED
@@ -1,24 +1,5 @@
1
+ inherit_gem:
2
+ dc-rubocop: default.yml
3
+
1
4
  AllCops:
2
5
  TargetRubyVersion: 2.6
3
- NewCops: enable
4
- SuggestExtensions: false
5
-
6
- Style/StringLiterals:
7
- Enabled: true
8
- EnforcedStyle: double_quotes
9
-
10
- Style/StringLiteralsInInterpolation:
11
- Enabled: true
12
- EnforcedStyle: double_quotes
13
-
14
- Style/GuardClause:
15
- Enabled: false
16
-
17
- Layout/LineLength:
18
- Max: 120
19
-
20
- Metrics:
21
- Enabled: false
22
-
23
- Style/Documentation:
24
- Enabled: false
data/Gemfile CHANGED
@@ -6,13 +6,6 @@ source "https://rubygems.org"
6
6
  gemspec
7
7
 
8
8
  group :development, optional: true do
9
- gem "guard", "~> 2.18"
10
- gem "guard-rake", "~> 1.0"
11
- gem "minitest", "~> 5.0"
12
- gem "rake", "~> 13.0"
9
+ gem "dc-devtools"
13
10
  gem "rdoc"
14
-
15
- gem "rubocop", "~> 1.21"
16
- gem "rubocop-minitest"
17
- gem "rubocop-rake"
18
11
  end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- plansheet (0.27.0)
4
+ plansheet (0.30.2)
5
5
  dc-kwalify (~> 1.0)
6
6
  diffy (= 3.4.2)
7
7
  rgl (= 0.5.8)
@@ -11,7 +11,18 @@ GEM
11
11
  specs:
12
12
  ast (2.4.2)
13
13
  coderay (1.1.3)
14
+ dc-devtools (0.0.202207232239)
15
+ dc-rubocop (>= 0.0.3)
16
+ guard (~> 2.18)
17
+ guard-rake (~> 1.0)
18
+ minitest
19
+ rake (~> 13.0)
20
+ rubocop (~> 1.30)
21
+ rubocop-minitest
22
+ rubocop-rake (~> 0.6)
14
23
  dc-kwalify (1.0.0)
24
+ dc-rubocop (0.0.3)
25
+ rubocop
15
26
  diffy (3.4.2)
16
27
  ffi (1.15.5)
17
28
  formatador (1.1.0)
@@ -28,13 +39,14 @@ GEM
28
39
  guard-rake (1.0.0)
29
40
  guard
30
41
  rake
42
+ json (2.6.2)
31
43
  lazy_priority_queue (0.1.1)
32
44
  listen (3.7.1)
33
45
  rb-fsevent (~> 0.10, >= 0.10.3)
34
46
  rb-inotify (~> 0.9, >= 0.9.10)
35
47
  lumberjack (1.2.8)
36
48
  method_source (1.0.0)
37
- minitest (5.15.0)
49
+ minitest (5.16.2)
38
50
  nenv (0.3.0)
39
51
  notiffany (0.1.3)
40
52
  nenv (~> 0.1)
@@ -54,24 +66,25 @@ GEM
54
66
  ffi (~> 1.0)
55
67
  rdoc (6.4.0)
56
68
  psych (>= 4.0.0)
57
- regexp_parser (2.4.0)
69
+ regexp_parser (2.5.0)
58
70
  rexml (3.2.5)
59
71
  rgl (0.5.8)
60
72
  lazy_priority_queue (~> 0.1.0)
61
73
  rexml (~> 3.2, >= 3.2.4)
62
74
  stream (~> 0.5.3)
63
- rubocop (1.29.1)
75
+ rubocop (1.32.0)
76
+ json (~> 2.3)
64
77
  parallel (~> 1.10)
65
78
  parser (>= 3.1.0.0)
66
79
  rainbow (>= 2.2.2, < 4.0)
67
80
  regexp_parser (>= 1.8, < 3.0)
68
81
  rexml (>= 3.2.5, < 4.0)
69
- rubocop-ast (>= 1.17.0, < 2.0)
82
+ rubocop-ast (>= 1.19.1, < 2.0)
70
83
  ruby-progressbar (~> 1.7)
71
84
  unicode-display_width (>= 1.4.0, < 3.0)
72
- rubocop-ast (1.18.0)
85
+ rubocop-ast (1.19.1)
73
86
  parser (>= 3.1.1.0)
74
- rubocop-minitest (0.20.0)
87
+ rubocop-minitest (0.20.1)
75
88
  rubocop (>= 0.90, < 2.0)
76
89
  rubocop-rake (0.6.0)
77
90
  rubocop (~> 1.0)
@@ -81,21 +94,15 @@ GEM
81
94
  generator
82
95
  stringio (3.0.2)
83
96
  thor (1.2.1)
84
- unicode-display_width (2.1.0)
97
+ unicode-display_width (2.2.0)
85
98
 
86
99
  PLATFORMS
87
100
  x86_64-linux
88
101
 
89
102
  DEPENDENCIES
90
- guard (~> 2.18)
91
- guard-rake (~> 1.0)
92
- minitest (~> 5.0)
103
+ dc-devtools
93
104
  plansheet!
94
- rake (~> 13.0)
95
105
  rdoc
96
- rubocop (~> 1.21)
97
- rubocop-minitest
98
- rubocop-rake
99
106
 
100
107
  BUNDLED WITH
101
108
  2.3.6
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Plansheet
2
2
 
3
- Plansheet is a simple to-do list generator program that creates a
4
- structured printout (a la [pandoc](https://pandoc.org)) from a
5
- pile of formatted [YAML](https://yaml.org) files. Still very much a work-in-progress
3
+ Plansheet is a simple to-do list generator program that creates a LaTeX
4
+ printout (that can be turned into a PDF by pdflatex) from a pile of formatted
5
+ [YAML](https://yaml.org) files.
6
+
7
+ Plansheet is still a work-in-progress, and while effort is made not to break over minor/patch versions, it's best effort. If you aren't comfortable with fixing Ruby using a lot of metaprogramming, check back in a year or two
6
8
 
7
9
  ## Installation
8
10
 
data/exe/plansheet CHANGED
@@ -29,6 +29,10 @@ parser.on(
29
29
  "--time-roi",
30
30
  "Show projects with a time return-on-investment"
31
31
  )
32
+ parser.on(
33
+ "--needs-thinking",
34
+ "Show projects that need tasks, time estimates, etc (WIP)"
35
+ )
32
36
  parser.on(
33
37
  "--calendar",
34
38
  "List of projects ordered by due date"
@@ -46,15 +50,18 @@ pool = Plansheet::Pool.new({ projects_dir: config["projects_dir"],
46
50
 
47
51
  if options[:sheet] || options.empty?
48
52
  require "plansheet/sheet"
49
- Dir.mkdir config["output_dir"] unless Dir.exist? config["output_dir"]
50
- Plansheet::Sheet.new("#{config["output_dir"]}/projects.md", pool.projects)
53
+ FileUtils.mkdir_p config["output_dir"]
54
+ Plansheet::LaTeXSheet.new("#{config["output_dir"]}/projects.tex", pool.projects)
51
55
  elsif options[:irb]
52
56
  binding.irb # rubocop:disable Lint/Debugger
53
57
  elsif options[:stats]
58
+ require "plansheet/time"
59
+ include Plansheet::TimeUtils # rubocop:disable Style/MixinUsage
54
60
  puts "# of projects: #{pool.projects.count}"
55
61
  puts "# of tasks: #{pool.projects.sum { |x| x&.tasks&.count || 0 }}"
56
62
  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"
63
+ time = Plansheet::TimeUtils.build_time_duration(pool.projects.sum { |x| x.time_estimate_minutes || 0 })
64
+ puts "combined time estimate: #{time}"
58
65
  elsif options[:sort]
59
66
  # Pool sorts projects, this now just matches old behaviour
60
67
  pool.write_projects
@@ -65,6 +72,15 @@ elsif options[:"time-roi"]
65
72
  puts "time ROI payoff: #{proj.time_roi_payoff}"
66
73
  puts "\n"
67
74
  end
75
+ elsif options[:"needs-thinking"]
76
+ puts "Projects with no tasks"
77
+ project_arr = pool.projects.sort
78
+ project_arr.delete_if(&:dropped_or_done?)
79
+ project_arr.delete_if(&:tasks)
80
+ project_arr.each do |proj|
81
+ puts proj
82
+ puts "\n"
83
+ end
68
84
  elsif options[:calendar]
69
85
  # TODO: add a project filter method
70
86
  project_arr = pool.projects
@@ -79,14 +79,14 @@ module Plansheet
79
79
 
80
80
  def archive_projects
81
81
  archive_dir = "#{@projects_dir}/archive/"
82
- Dir.mkdir archive_dir unless Dir.exist? archive_dir
82
+ FileUtils.mkdir_p archive_dir
83
83
 
84
84
  # NOTE: It would save writes if we sorted and did all month/namespaces
85
85
  # writes only once, but as the normal case only sees a few projects
86
86
  # archived at a time, I'll leave that for someone else to implement ;-)
87
87
  projects_to_archive = @projects.select(&:archivable?)
88
88
  projects_to_archive.each do |project|
89
- path = Pathname.new "#{archive_dir}/#{project.completed_on_month}/#{project.namespace}.yml"
89
+ path = Pathname.new "#{archive_dir}/#{project.archive_month}/#{project.namespace}.yml"
90
90
  Dir.mkdir path.dirname unless path.dirname.exist?
91
91
  pyf = ProjectYAMLFile.new path
92
92
  pyf.append_project project
@@ -42,6 +42,8 @@ module Plansheet
42
42
  - blocked # project is blocked by another project, but otherwise ready/wip
43
43
  - planning # project in planning phase (set manually)
44
44
  - idea # project is little more than an idea
45
+ - paused # project shouldn't be archived, but should be excluded from
46
+ # generated output unless paused projects are requested
45
47
  - dropped # project has been explicitly dropped, but
46
48
  # want to keep around for reference, etc
47
49
  - done # project is finished, but want to keep around
@@ -100,6 +102,12 @@ module Plansheet
100
102
  "completed_on":
101
103
  desc: When the (non-recurring) project was completed
102
104
  type: date
105
+ "dropped_on":
106
+ desc: When the project was dropped
107
+ type: date
108
+ "paused_on":
109
+ desc: When the project was paused
110
+ type: date
103
111
  "created_on":
104
112
  desc: When the project was created
105
113
  type: date
@@ -4,6 +4,7 @@ require "yaml"
4
4
  require "date"
5
5
  require_relative "project/yaml"
6
6
  require_relative "project/stringify"
7
+ require_relative "./time"
7
8
 
8
9
  # Needed for Project#time_estimate, would be much happier *not* patching Array
9
10
  class Array
@@ -20,8 +21,9 @@ module Plansheet
20
21
  "waiting" => 4,
21
22
  "planning" => 5,
22
23
  "idea" => 6,
23
- "dropped" => 7,
24
- "done" => 8
24
+ "paused" => 7,
25
+ "dropped" => 8,
26
+ "done" => 9
25
27
  }.freeze
26
28
 
27
29
  # Pre-compute the next days-of-week
@@ -30,36 +32,6 @@ module Plansheet
30
32
  [d.strftime("%A"), d]
31
33
  end.freeze
32
34
 
33
- def self.parse_date_duration(str)
34
- return Regexp.last_match(1).to_i if str.strip.match(/(\d+)[dD]/)
35
- return (Regexp.last_match(1).to_i * 7) if str.strip.match(/(\d+)[wW]/)
36
-
37
- raise "Can't parse time duration string #{str}"
38
- end
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
-
63
35
  # The use of instance_variable_set/get probably seems a bit weird, but the
64
36
  # intent is to avoid object allocation on non-existent project properties, as
65
37
  # well as avoiding a bunch of copy-paste boilerplate when adding a new
@@ -68,6 +40,7 @@ module Plansheet
68
40
  # unwrap the loops if it's not needed.
69
41
  class Project
70
42
  include Comparable
43
+ include Plansheet::TimeUtils
71
44
 
72
45
  TIME_EST_REGEX = /\((\d+\.?\d*[mMhH])\)$/.freeze
73
46
  TIME_EST_REGEX_NO_CAPTURE = /\(\d+\.?\d*[mMhH]\)$/.freeze
@@ -83,7 +56,8 @@ module Plansheet
83
56
  # namespace is derived from file name
84
57
  STRING_PROPERTIES = %w[priority status location notes time_estimate daily_time_roi weekly_time_roi yearly_time_roi
85
58
  day_of_week frequency last_for lead_time].freeze
86
- DATE_PROPERTIES = %w[due defer completed_on created_on starts_on last_done last_reviewed].freeze
59
+ DATE_PROPERTIES = %w[due defer paused_on dropped_on completed_on created_on starts_on last_done
60
+ last_reviewed].freeze
87
61
  ARRAY_PROPERTIES = %w[dependencies externals urls tasks done tags].freeze
88
62
 
89
63
  ALL_PROPERTIES = STRING_PROPERTIES + DATE_PROPERTIES + ARRAY_PROPERTIES
@@ -132,15 +106,15 @@ module Plansheet
132
106
  if @tasks
133
107
  @time_estimate_minutes = @tasks&.select do |t|
134
108
  t.match? TIME_EST_REGEX_NO_CAPTURE
135
- end&.nil_if_empty&.map { |t| Plansheet::Project.task_time_estimate(t) }&.sum
109
+ end&.nil_if_empty&.map { |t| task_time_estimate(t) }&.sum
136
110
  elsif @time_estimate
137
111
  # No tasks with estimates, but there's an explicit time_estimate
138
112
  # Convert the field to minutes
139
- @time_estimate_minutes = Plansheet.parse_time_duration(@time_estimate)
113
+ @time_estimate_minutes = parse_time_duration(@time_estimate)
140
114
  end
141
115
  if @time_estimate_minutes
142
116
  # Rewrite time_estimate field
143
- @time_estimate = Plansheet.build_time_duration(@time_estimate_minutes)
117
+ @time_estimate = build_time_duration(@time_estimate_minutes)
144
118
 
145
119
  yms = yearly_minutes_saved
146
120
  @time_roi_payoff = yms.to_f / @time_estimate_minutes if yms
@@ -152,20 +126,26 @@ module Plansheet
152
126
  remove_instance_variable("@time_estimate") if @time_estimate
153
127
  remove_instance_variable("@time_estimate_minutes") if @time_estimate
154
128
  remove_instance_variable("@time_roi_payoff") if @time_roi_payoff
129
+ elsif paused?
130
+ @paused_on ||= Date.today
131
+ remove_instance_variable("@status") if @status
132
+ elsif dropped?
133
+ @dropped_on ||= Date.today
134
+ remove_instance_variable("@status") if @status
155
135
  end
156
136
  end
157
137
 
158
- def completed_on_month
159
- @completed_on&.strftime("%Y-%m")
138
+ def archive_month
139
+ @completed_on&.strftime("%Y-%m") || Date.today.strftime("%Y-%m")
160
140
  end
161
141
 
162
142
  def yearly_minutes_saved
163
143
  if @daily_time_roi
164
- Plansheet.parse_time_duration(@daily_time_roi) * 365
144
+ parse_time_duration(@daily_time_roi) * 365
165
145
  elsif @weekly_time_roi
166
- Plansheet.parse_time_duration(@weekly_time_roi) * 52
146
+ parse_time_duration(@weekly_time_roi) * 52
167
147
  elsif @yearly_time_roi
168
- Plansheet.parse_time_duration(@yearly_time_roi)
148
+ parse_time_duration(@yearly_time_roi)
169
149
  end
170
150
  end
171
151
 
@@ -253,14 +233,16 @@ module Plansheet
253
233
  # Projects that are dropped or done are considered "complete", insofar as
254
234
  # they are only kept around for later reference.
255
235
  def compare_completeness(other)
256
- return 0 if dropped_or_done? && other.dropped_or_done?
257
- return 0 if !dropped_or_done? && !other.dropped_or_done?
258
-
259
- dropped_or_done? ? 1 : -1
236
+ retval = 0
237
+ retval += 1 if dropped_or_done?
238
+ retval -= 1 if other.dropped_or_done?
239
+ retval
260
240
  end
261
241
 
262
242
  def status
263
243
  return @status if @status
244
+ return "dropped" if @dropped_on
245
+ return "paused" if @paused_on
264
246
  return recurring_status if recurring?
265
247
  return task_based_status if @tasks || @done
266
248
  return "done" if @completed_on && @tasks.nil?
@@ -309,7 +291,7 @@ module Plansheet
309
291
 
310
292
  def recurring_due_date
311
293
  if @last_done
312
- return @last_done + Plansheet.parse_date_duration(@frequency) if @frequency
294
+ return @last_done + parse_date_duration(@frequency) if @frequency
313
295
 
314
296
  if @day_of_week
315
297
  return Date.today + 7 if @last_done == Date.today
@@ -332,13 +314,13 @@ module Plansheet
332
314
  end
333
315
 
334
316
  def last_for_deferral
335
- return @last_done + Plansheet.parse_date_duration(@last_for) if @last_done
317
+ return @last_done + parse_date_duration(@last_for) if @last_done
336
318
 
337
319
  Date.today
338
320
  end
339
321
 
340
322
  def lead_time_deferral
341
- [(due - Plansheet.parse_date_duration(@lead_time)),
323
+ [(due - parse_date_duration(@lead_time)),
342
324
  Date.today].max
343
325
  end
344
326
 
@@ -350,20 +332,28 @@ module Plansheet
350
332
  !@frequency.nil? || !@day_of_week.nil? || !@last_done.nil? || !@last_for.nil?
351
333
  end
352
334
 
353
- def dropped_or_done?
354
- status == "dropped" || status == "done"
335
+ def paused?
336
+ status == "paused"
337
+ end
338
+
339
+ def dropped?
340
+ status == "dropped"
355
341
  end
356
342
 
357
343
  def done?
358
344
  status == "done"
359
345
  end
360
346
 
347
+ def dropped_or_done?
348
+ dropped? || done?
349
+ end
350
+
361
351
  def archivable?
362
- !recurring? && @completed_on
352
+ (!recurring? && @completed_on) || dropped?
363
353
  end
364
354
 
365
- def self.task_time_estimate(str)
366
- Plansheet.parse_time_duration(Regexp.last_match(1)) if str.match(TIME_EST_REGEX)
355
+ def task_time_estimate(str)
356
+ parse_time_duration(Regexp.last_match(1)) if str.match(TIME_EST_REGEX)
367
357
  end
368
358
 
369
359
  def to_h
@@ -3,7 +3,7 @@
3
3
  require "date"
4
4
  module Plansheet
5
5
  # The Sheet class constructs a Markdown/LaTeX file for use with pandoc
6
- class Sheet
6
+ class LaTeXSheet
7
7
  def initialize(output_file, project_arr)
8
8
  projects_str = String.new
9
9
  projects_str << sheet_header
@@ -12,20 +12,64 @@ module Plansheet
12
12
  projects_str << project_minipage(p)
13
13
  end
14
14
  puts "Writing to #{output_file}"
15
+ projects_str << sheet_footer
15
16
  File.write(output_file, projects_str)
16
17
  end
17
18
 
18
19
  def sheet_header
20
+ # LaTeX used to be generated by pandoc
19
21
  <<~FRONTMATTER
20
- ---
21
- geometry: margin=1.5cm
22
- ---
22
+ \\PassOptionsToPackage{unicode}{hyperref}
23
+ \\PassOptionsToPackage{hyphens}{url}
24
+ \\documentclass[]{article}
25
+ \\author{}
26
+ \\date{#{Date.today}}
27
+ \\usepackage{amsmath,amssymb}
28
+ \\usepackage{lmodern}
29
+ \\usepackage{iftex}
30
+ \\usepackage[T1]{fontenc}
31
+ \\usepackage[utf8]{inputenc}
32
+ \\usepackage{textcomp}
33
+ \\IfFileExists{upquote.sty}{\\usepackage{upquote}}{}
34
+ \\IfFileExists{microtype.sty}{
35
+ \\usepackage[]{microtype}
36
+ \\UseMicrotypeSet[protrusion]{basicmath}
37
+ }{}
38
+ \\makeatletter
39
+ \\@ifundefined{KOMAClassName}{
40
+ \\IfFileExists{parskip.sty}{
41
+ \\usepackage{parskip}
42
+ }{
43
+ \\setlength{\\parindent}{0pt}
44
+ \\setlength{\\parskip}{6pt plus 2pt minus 1pt}}
45
+ }{
46
+ \\KOMAoptions{parskip=half}}
47
+ \\makeatother
48
+ \\usepackage{xcolor}
49
+ \\IfFileExists{xurl.sty}{\\usepackage{xurl}}{}
50
+ \\IfFileExists{bookmark.sty}{\\usepackage{bookmark}}{\\usepackage{hyperref}}
51
+ \\hypersetup{
52
+ hidelinks,
53
+ pdfcreator={LaTeX via plansheet}}
54
+ \\urlstyle{same}
55
+ \\usepackage[margin=1.5cm]{geometry}
56
+ \\setlength{\\emergencystretch}{3em}
57
+ \\providecommand{\\tightlist}{
58
+ \\setlength{\\itemsep}{0pt}\\setlength{\\parskip}{0pt}}
59
+ \\setcounter{secnumdepth}{-\\maxdimen}
60
+
61
+ \\begin{document}
62
+
23
63
  \\thispagestyle{empty}
24
64
 
25
- # Date: #{Date.today}
65
+ \\section{Date: #{Date.today}}
26
66
  FRONTMATTER
27
67
  end
28
68
 
69
+ def sheet_footer
70
+ '\end{document}'
71
+ end
72
+
29
73
  def project_minipage(proj)
30
74
  str = String.new
31
75
  str << "\\begin{minipage}{6cm}\n"
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plansheet
4
+ module TimeUtils
5
+ def parse_date_duration(str)
6
+ return Regexp.last_match(1).to_i if str.strip.match(/(\d+)[dD]/)
7
+ return (Regexp.last_match(1).to_i * 7) if str.strip.match(/(\d+)[wW]/)
8
+
9
+ raise "Can't parse time duration string #{str}"
10
+ end
11
+
12
+ def parse_time_duration(str)
13
+ if str.match(/(\d+h) (\d+m)/)
14
+ return (parse_time_duration(Regexp.last_match(1)) + parse_time_duration(Regexp.last_match(2)))
15
+ end
16
+
17
+ return Regexp.last_match(1).to_i if str.strip.match(/(\d+)m/)
18
+ return (Regexp.last_match(1).to_f * 60).to_i if str.strip.match(/(\d+\.?\d*)h/)
19
+
20
+ raise "Can't parse time duration string #{str}"
21
+ end
22
+
23
+ def build_time_duration(minutes)
24
+ if minutes > 59
25
+ if (minutes % 60).zero?
26
+ "#{minutes / 60}h"
27
+ else
28
+ "#{minutes / 60}h #{minutes % 60}m"
29
+ end
30
+ else
31
+ "#{minutes}m"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Plansheet
4
- VERSION = "0.27.0"
4
+ VERSION = "0.30.2"
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.27.0
4
+ version: 0.30.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-07-15 00:00:00.000000000 Z
11
+ date: 2022-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dc-kwalify
@@ -77,6 +77,7 @@ files:
77
77
  - lib/plansheet/project/yaml.rb
78
78
  - lib/plansheet/sheet.rb
79
79
  - lib/plansheet/sheet/latex.rb
80
+ - lib/plansheet/time.rb
80
81
  - lib/plansheet/version.rb
81
82
  homepage: https://dafyddcrosby.com
82
83
  licenses: