plansheet 0.31.0 → 0.34.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Rakefile +1 -3
- data/exe/plansheet +18 -6
- data/lib/plansheet/project.rb +19 -20
- data/lib/plansheet/sheet/daily.rb +83 -0
- data/lib/plansheet/sheet/latex.rb +17 -75
- data/lib/plansheet/sheet/latex_utils.rb +112 -0
- data/lib/plansheet/sheet/monthly.rb +110 -0
- data/lib/plansheet/sheet/weekly.rb +132 -0
- data/lib/plansheet/sheet.rb +4 -0
- data/lib/plansheet/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0127f89bb4b286c6bda0d5ff1d1c8e02d4ceb26f0898f78f2031ad7d69913bf1
|
4
|
+
data.tar.gz: 0ce4e76d7693d51bfdd96cd6390124955858c15b5fe109f03d11d4aa707bdf98
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d694e749bff3c2a4b7f6983f75ecc6861eb8a793752658439bc0e2a3381c0037aa09edad3ab4f33fc8365992ee21ab12bdc450eb6283a4e1fa8b2f7250078a3
|
7
|
+
data.tar.gz: 81e8c85c1914e225b2bca1fe8655e367b1d36a830ea0905d0bf449fa4c3ced79b023c92ecd9d655cf09a5843ec9be78de67a63e5efdaa341e843999dec736eb6
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
data/exe/plansheet
CHANGED
@@ -4,11 +4,14 @@
|
|
4
4
|
require "plansheet"
|
5
5
|
require "optparse"
|
6
6
|
|
7
|
+
options = {}
|
7
8
|
parser = OptionParser.new
|
8
9
|
parser.on(
|
9
|
-
"--sheet",
|
10
|
+
"--sheet [TYPE]",
|
10
11
|
"Generates MD/LaTeX project PDF"
|
11
|
-
)
|
12
|
+
) do |sheet_type|
|
13
|
+
options[:sheet] = sheet_type || "projects"
|
14
|
+
end
|
12
15
|
parser.on(
|
13
16
|
"--sort",
|
14
17
|
"Sort project files"
|
@@ -41,7 +44,6 @@ parser.on(
|
|
41
44
|
"--location_filter LOCATION",
|
42
45
|
"location filter for CLI dump (WIP)"
|
43
46
|
)
|
44
|
-
options = {}
|
45
47
|
parser.parse!(into: options)
|
46
48
|
|
47
49
|
config = Plansheet.load_config
|
@@ -49,11 +51,21 @@ pool = Plansheet::Pool.new({ projects_dir: config["projects_dir"],
|
|
49
51
|
sort_order: config["sort_order"] })
|
50
52
|
|
51
53
|
if options[:sheet] || options.empty?
|
54
|
+
# TODO: Add pdflatex command (customizable path)
|
52
55
|
require "plansheet/sheet"
|
53
56
|
FileUtils.mkdir_p config["output_dir"]
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
+
case options[:sheet]
|
58
|
+
when "daily"
|
59
|
+
Plansheet::DailyLaTeXSheet.new("#{config["output_dir"]}/daily.tex", config["daily"])
|
60
|
+
when "weekly"
|
61
|
+
Plansheet::WeeklyLaTeXSheet.new("#{config["output_dir"]}/weekly.tex", pool.projects, config["weekly"])
|
62
|
+
when "monthly"
|
63
|
+
Plansheet::MonthlyLaTeXSheet.new("#{config["output_dir"]}/monthly.tex", config["monthly"])
|
64
|
+
else
|
65
|
+
project_arr = pool.projects
|
66
|
+
project_arr.delete_if { |x| %w[dropped done paused].include? x.status }
|
67
|
+
Plansheet::LaTeXSheet.new("#{config["output_dir"]}/projects.tex", project_arr)
|
68
|
+
end
|
57
69
|
elsif options[:irb]
|
58
70
|
binding.irb # rubocop:disable Lint/Debugger
|
59
71
|
elsif options[:stats]
|
data/lib/plansheet/project.rb
CHANGED
@@ -6,14 +6,17 @@ require_relative "project/yaml"
|
|
6
6
|
require_relative "project/stringify"
|
7
7
|
require_relative "./time"
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
module Plansheet
|
10
|
+
module PlansheetArray
|
11
|
+
# Needed for Project#time_estimate
|
12
|
+
refine Array do
|
13
|
+
def nil_if_empty
|
14
|
+
count.zero? ? nil : self
|
15
|
+
end
|
16
|
+
end
|
13
17
|
end
|
14
|
-
|
18
|
+
using PlansheetArray
|
15
19
|
|
16
|
-
module Plansheet
|
17
20
|
PROJECT_STATUS_PRIORITY = {
|
18
21
|
"wip" => 1,
|
19
22
|
"ready" => 2,
|
@@ -121,11 +124,13 @@ module Plansheet
|
|
121
124
|
end
|
122
125
|
|
123
126
|
if done?
|
124
|
-
@completed_on ||= Date.today unless recurring?
|
125
127
|
remove_instance_variable("@status") if @status
|
126
|
-
|
127
|
-
|
128
|
-
|
128
|
+
unless recurring?
|
129
|
+
@completed_on ||= Date.today
|
130
|
+
remove_instance_variable("@time_estimate") if @time_estimate
|
131
|
+
remove_instance_variable("@time_estimate_minutes") if @time_estimate
|
132
|
+
remove_instance_variable("@time_roi_payoff") if @time_roi_payoff
|
133
|
+
end
|
129
134
|
elsif paused?
|
130
135
|
@paused_on ||= Date.today
|
131
136
|
remove_instance_variable("@status") if @status
|
@@ -332,16 +337,10 @@ module Plansheet
|
|
332
337
|
!@frequency.nil? || !@day_of_week.nil? || !@last_done.nil? || !@last_for.nil?
|
333
338
|
end
|
334
339
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
def dropped?
|
340
|
-
status == "dropped"
|
341
|
-
end
|
342
|
-
|
343
|
-
def done?
|
344
|
-
status == "done"
|
340
|
+
PROJECT_STATUS_PRIORITY.each_key do |stat|
|
341
|
+
define_method("#{stat}?".to_sym) do
|
342
|
+
status == stat
|
343
|
+
end
|
345
344
|
end
|
346
345
|
|
347
346
|
def dropped_or_done?
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
module Plansheet
|
5
|
+
class DailyLaTeXSheet
|
6
|
+
include Plansheet::LaTeXMixins
|
7
|
+
def initialize(output_file, config)
|
8
|
+
@config = config
|
9
|
+
projects_str = sheet_header
|
10
|
+
projects_str << document do
|
11
|
+
sheet_body
|
12
|
+
end
|
13
|
+
|
14
|
+
puts "Writing to #{output_file}"
|
15
|
+
File.write(output_file, projects_str)
|
16
|
+
end
|
17
|
+
|
18
|
+
def dateline
|
19
|
+
<<~DATELINE
|
20
|
+
\\thispagestyle{empty}
|
21
|
+
|
22
|
+
Date \\(\\underline{\\hspace{4cm}}\\)
|
23
|
+
|
24
|
+
DATELINE
|
25
|
+
end
|
26
|
+
|
27
|
+
INCOLUMN_SPACING = "7mm".freeze
|
28
|
+
def sheet_body
|
29
|
+
[
|
30
|
+
dateline,
|
31
|
+
vspace("1cm"),
|
32
|
+
vbox do
|
33
|
+
@config["top"].map do |section|
|
34
|
+
minipage("8cm") do
|
35
|
+
section_output(section)
|
36
|
+
end
|
37
|
+
end.join
|
38
|
+
end,
|
39
|
+
|
40
|
+
%w[left_bar right_bar].map do |col|
|
41
|
+
minipage("8cm") do
|
42
|
+
@config[col].map do |section|
|
43
|
+
minipage("\\textwidth") do
|
44
|
+
section_output(section)
|
45
|
+
end
|
46
|
+
end.join(vspace(INCOLUMN_SPACING))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
].flatten.join
|
50
|
+
end
|
51
|
+
|
52
|
+
def section_output(section)
|
53
|
+
case section["type"]
|
54
|
+
when "checkboxes"
|
55
|
+
checkboxes(section["items"])
|
56
|
+
when "checkbox_and_lines"
|
57
|
+
checkbox_and_lines(section["items"])
|
58
|
+
when "multiline_checkbox_and_lines"
|
59
|
+
multiline_checkbox_and_lines(section["items"])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def checkboxes(items)
|
64
|
+
items.map do |l|
|
65
|
+
l.map do |i|
|
66
|
+
checkbox_item i
|
67
|
+
end.join(" ")
|
68
|
+
end.join(HARD_NL)
|
69
|
+
end
|
70
|
+
|
71
|
+
def checkbox_and_lines(items)
|
72
|
+
items.map do |i|
|
73
|
+
checkbox_item(i, line: "4cm")
|
74
|
+
end.join(HARD_NL)
|
75
|
+
end
|
76
|
+
|
77
|
+
def multiline_checkbox_and_lines(items)
|
78
|
+
items.map do |q|
|
79
|
+
"#{checkbox_item q} #{HARD_NL}#{writein_line("\\textwidth")}"
|
80
|
+
end.join(HARD_NL)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -2,97 +2,39 @@
|
|
2
2
|
|
3
3
|
require "date"
|
4
4
|
module Plansheet
|
5
|
-
# The Sheet class constructs a Markdown/LaTeX file for use with
|
5
|
+
# The Sheet class constructs a Markdown/LaTeX file for use with pdflatex
|
6
6
|
class LaTeXSheet
|
7
|
+
include LaTeXMixins
|
7
8
|
def initialize(output_file, project_arr)
|
8
|
-
projects_str =
|
9
|
-
projects_str <<
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
projects_str = sheet_header
|
10
|
+
projects_str << document do
|
11
|
+
"\\thispagestyle{empty}\n\n\\section{Date: #{Date.today}}\n".concat(
|
12
|
+
project_arr.map do |p|
|
13
|
+
project_minipage(p)
|
14
|
+
end.join
|
15
|
+
)
|
13
16
|
end
|
14
17
|
puts "Writing to #{output_file}"
|
15
|
-
projects_str << sheet_footer
|
16
18
|
File.write(output_file, projects_str)
|
17
19
|
end
|
18
20
|
|
19
|
-
def sheet_header
|
20
|
-
# LaTeX used to be generated by pandoc
|
21
|
-
<<~FRONTMATTER
|
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
|
-
|
63
|
-
\\thispagestyle{empty}
|
64
|
-
|
65
|
-
\\section{Date: #{Date.today}}
|
66
|
-
FRONTMATTER
|
67
|
-
end
|
68
|
-
|
69
|
-
def sheet_footer
|
70
|
-
'\end{document}'
|
71
|
-
end
|
72
|
-
|
73
21
|
def project_minipage(proj)
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
22
|
+
minipage("6cm") do
|
23
|
+
project_header(proj)&.concat(
|
24
|
+
proj&.tasks&.map do |t|
|
25
|
+
"#{checkbox_item sanitize_string(t)}#{HARD_NL}"
|
26
|
+
end&.join("") || "" # empty string to catch nil
|
27
|
+
)
|
79
28
|
end
|
80
|
-
str << "\\end{minipage}\n"
|
81
|
-
str
|
82
|
-
end
|
83
|
-
|
84
|
-
def sanitize_string(str)
|
85
|
-
str.gsub("_", '\_')
|
86
29
|
end
|
87
30
|
|
88
31
|
def project_header(proj)
|
89
|
-
str =
|
90
|
-
str << "#{sanitize_string(proj.namespace)}: #{sanitize_string(proj.name)}\\\\\n"
|
32
|
+
str = "#{sanitize_string(proj.namespace)}: #{sanitize_string(proj.name)}#{HARD_NL}"
|
91
33
|
str << proj.status.to_s
|
92
34
|
str << " - #{sanitize_string(proj.location)}" if proj.location
|
93
35
|
str << " due: #{proj.due}" if proj.due
|
94
36
|
str << " time: #{proj.time_estimate}" if proj.time_estimate
|
95
|
-
str <<
|
37
|
+
str << HARD_NL
|
96
38
|
str
|
97
39
|
end
|
98
40
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
module Plansheet
|
5
|
+
module LaTeXMixins
|
6
|
+
HARD_NL = " \\\\\n"
|
7
|
+
SQUARE = "$\\square$"
|
8
|
+
|
9
|
+
def vspace(space)
|
10
|
+
"\\vspace{#{space}}\n"
|
11
|
+
end
|
12
|
+
|
13
|
+
def writein_line(space)
|
14
|
+
"$\\underline{\\hspace{#{space}}}$"
|
15
|
+
end
|
16
|
+
|
17
|
+
def checkbox_item(str, line: nil)
|
18
|
+
"#{SQUARE} #{str}#{" #{writein_line(line)}" if line}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def vbox
|
22
|
+
"\\vbox{\n#{yield}\n}\n"
|
23
|
+
end
|
24
|
+
|
25
|
+
%w[center document enumerate tabbing].each do |env|
|
26
|
+
define_method(env.to_sym) do |&e|
|
27
|
+
<<~ENV
|
28
|
+
\\begin{#{env}}
|
29
|
+
#{e.call}
|
30
|
+
\\end{#{env}}
|
31
|
+
ENV
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def itemize_tightlist
|
36
|
+
<<~DOCUMENT
|
37
|
+
\\begin{itemize}
|
38
|
+
\\tightlist
|
39
|
+
#{yield}
|
40
|
+
\\end{itemize}
|
41
|
+
DOCUMENT
|
42
|
+
end
|
43
|
+
|
44
|
+
def itemline(item, opt: nil)
|
45
|
+
"\\item#{opt ? "[#{opt}]" : ""} #{item}\n"
|
46
|
+
end
|
47
|
+
|
48
|
+
def minipage(size)
|
49
|
+
<<~MINIPAGE
|
50
|
+
\\begin{minipage}{#{size}}
|
51
|
+
#{yield}
|
52
|
+
\\end{minipage}
|
53
|
+
MINIPAGE
|
54
|
+
end
|
55
|
+
|
56
|
+
def sanitize_string(str)
|
57
|
+
str.gsub("_", '\_')
|
58
|
+
end
|
59
|
+
|
60
|
+
def sheet_header
|
61
|
+
# LaTeX used to be generated by pandoc
|
62
|
+
<<~FRONTMATTER
|
63
|
+
\\PassOptionsToPackage{unicode}{hyperref}
|
64
|
+
\\PassOptionsToPackage{hyphens}{url}
|
65
|
+
\\documentclass[]{article}
|
66
|
+
\\author{}
|
67
|
+
\\date{#{Date.today}}
|
68
|
+
\\usepackage{amsmath,amssymb}
|
69
|
+
\\usepackage{lmodern}
|
70
|
+
\\usepackage{iftex}
|
71
|
+
\\usepackage[T1]{fontenc}
|
72
|
+
\\usepackage[utf8]{inputenc}
|
73
|
+
\\usepackage{textcomp}
|
74
|
+
\\IfFileExists{upquote.sty}{\\usepackage{upquote}}{}
|
75
|
+
\\IfFileExists{microtype.sty}{
|
76
|
+
\\usepackage[]{microtype}
|
77
|
+
\\UseMicrotypeSet[protrusion]{basicmath}
|
78
|
+
}{}
|
79
|
+
\\makeatletter
|
80
|
+
\\@ifundefined{KOMAClassName}{
|
81
|
+
\\IfFileExists{parskip.sty}{
|
82
|
+
\\usepackage{parskip}
|
83
|
+
}{
|
84
|
+
\\setlength{\\parindent}{0pt}
|
85
|
+
\\setlength{\\parskip}{6pt plus 2pt minus 1pt}}
|
86
|
+
}{
|
87
|
+
\\KOMAoptions{parskip=half}}
|
88
|
+
\\makeatother
|
89
|
+
\\usepackage{xcolor}
|
90
|
+
\\IfFileExists{xurl.sty}{\\usepackage{xurl}}{}
|
91
|
+
\\IfFileExists{bookmark.sty}{\\usepackage{bookmark}}{\\usepackage{hyperref}}
|
92
|
+
\\hypersetup{
|
93
|
+
hidelinks,
|
94
|
+
pdfcreator={LaTeX via plansheet}}
|
95
|
+
\\urlstyle{same}
|
96
|
+
\\usepackage[margin=1.5cm]{geometry}
|
97
|
+
\\setlength{\\emergencystretch}{3em}
|
98
|
+
\\providecommand{\\tightlist}{
|
99
|
+
\\setlength{\\itemsep}{0pt}\\setlength{\\parskip}{0pt}}
|
100
|
+
\\setcounter{secnumdepth}{-\\maxdimen}
|
101
|
+
FRONTMATTER
|
102
|
+
end
|
103
|
+
|
104
|
+
def config_file_sanity_check(options = {})
|
105
|
+
options.each do |opt, type|
|
106
|
+
unless @config[opt].is_a?(type) && !@config[opt]&.empty?
|
107
|
+
abort "Need to specify #{opt} #{type.to_s.downcase} in your ~/.plansheet.yml config"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
module Plansheet
|
5
|
+
class MonthlyLaTeXSheet
|
6
|
+
include LaTeXMixins
|
7
|
+
def initialize(output_file, config)
|
8
|
+
@config = config || {}
|
9
|
+
config_file_sanity_check({
|
10
|
+
"highlevel_items" => Array,
|
11
|
+
"tags" => Hash
|
12
|
+
})
|
13
|
+
|
14
|
+
str = sheet_header
|
15
|
+
str << document do
|
16
|
+
[
|
17
|
+
month_line,
|
18
|
+
highlevel_items,
|
19
|
+
center do
|
20
|
+
[
|
21
|
+
events_minipage,
|
22
|
+
tags_minipages
|
23
|
+
].join
|
24
|
+
end,
|
25
|
+
monthly_calendar_grid
|
26
|
+
].join
|
27
|
+
end
|
28
|
+
puts "Writing to #{output_file}"
|
29
|
+
File.write(output_file, str)
|
30
|
+
end
|
31
|
+
|
32
|
+
def highlevel_items
|
33
|
+
itemize_tightlist do
|
34
|
+
@config["highlevel_items"].map do |i|
|
35
|
+
itemline("#{i} #{writein_line("5cm")}", opt: SQUARE)
|
36
|
+
end.join.concat("\n")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def tags_minipages
|
41
|
+
minipage("6.5cm") do
|
42
|
+
@config["tags"].map { |k, v| tag_minipage k, v }.join
|
43
|
+
end.concat("\n")
|
44
|
+
end
|
45
|
+
|
46
|
+
def events_minipage
|
47
|
+
minipage("8cm") do
|
48
|
+
"Events:\n#{
|
49
|
+
itemize_tightlist do
|
50
|
+
30.times.map do |n|
|
51
|
+
itemline(
|
52
|
+
writein_line("6cm"),
|
53
|
+
opt: (Date.today + n).strftime("%a %m-%d")
|
54
|
+
)
|
55
|
+
end.join.concat("\n")
|
56
|
+
end
|
57
|
+
}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def pretty_tag_name(tag)
|
62
|
+
tag.capitalize.gsub("_", " ")
|
63
|
+
end
|
64
|
+
|
65
|
+
def tag_minipage(tag, items = 10)
|
66
|
+
"#{pretty_tag_name tag}:\n".concat(
|
67
|
+
itemize_tightlist do
|
68
|
+
items.times.map do
|
69
|
+
itemline(writein_line("6cm"), opt: SQUARE)
|
70
|
+
end.join
|
71
|
+
end
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def month_line
|
76
|
+
"For the next month: #{Date.today} - #{Date.today + 30}\n\n"
|
77
|
+
end
|
78
|
+
|
79
|
+
def monthly_calendar_grid
|
80
|
+
<<~GRID
|
81
|
+
\\begin{tabular}{|#{7.times.map { "p{2cm}" }.join("|")}|}
|
82
|
+
\\hline
|
83
|
+
GRID
|
84
|
+
.concat(
|
85
|
+
%w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday].join(" & ").concat(HARD_NL)
|
86
|
+
).concat(
|
87
|
+
5.times.map do |n|
|
88
|
+
calendar_grid_line n
|
89
|
+
end.join(HARD_NL).concat(HARD_NL)
|
90
|
+
).concat(
|
91
|
+
<<~GRID
|
92
|
+
\\hline
|
93
|
+
\\end{tabular}
|
94
|
+
GRID
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def calendar_grid_line(week_multiplier = 0)
|
101
|
+
"\\hline\n#{
|
102
|
+
7.times.map do |n|
|
103
|
+
(Date.today - Date.today.wday + (n + (week_multiplier * 7))).strftime("%d")
|
104
|
+
end.join(" & ")
|
105
|
+
} #{
|
106
|
+
vspace("5mm")
|
107
|
+
}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
module Plansheet
|
5
|
+
class WeeklyLaTeXSheet
|
6
|
+
include LaTeXMixins
|
7
|
+
include TimeUtils
|
8
|
+
MINIPAGE_SIZE = "6cm".freeze
|
9
|
+
def initialize(output_file, projects, config)
|
10
|
+
@config = config || {}
|
11
|
+
config_file_sanity_check({
|
12
|
+
"namespaces" => Hash,
|
13
|
+
"tags" => Hash
|
14
|
+
})
|
15
|
+
|
16
|
+
@external_commits = projects.select(&:externals) || []
|
17
|
+
@projects = projects # TODO: remove finished?
|
18
|
+
str = sheet_header
|
19
|
+
str << document do
|
20
|
+
[
|
21
|
+
week_line,
|
22
|
+
[
|
23
|
+
event_writeins,
|
24
|
+
past_due,
|
25
|
+
upcoming_due
|
26
|
+
].compact.join,
|
27
|
+
# Since the following are a lot more volatile in minipage length,
|
28
|
+
# sort by newline so more fits on the page
|
29
|
+
[
|
30
|
+
external_commits,
|
31
|
+
works_in_progress,
|
32
|
+
tag_minipages,
|
33
|
+
namespace_minipages,
|
34
|
+
recurring_defer
|
35
|
+
].flatten.compact.sort_by { |x| x.count("\n") }.join
|
36
|
+
].flatten.join
|
37
|
+
end
|
38
|
+
puts "Writing to #{output_file}"
|
39
|
+
File.write(output_file, str)
|
40
|
+
end
|
41
|
+
|
42
|
+
def namespace_minipages
|
43
|
+
@config["namespaces"].map do |namespace, limit|
|
44
|
+
projects = @projects.select { |x| x.namespace == namespace }
|
45
|
+
project_minipage namespace, projects_in_time(projects, limit)
|
46
|
+
end&.join
|
47
|
+
end
|
48
|
+
|
49
|
+
def tag_minipages
|
50
|
+
@config["tags"].map do |tag, limit|
|
51
|
+
projects = @projects.select { |x| x&.tags&.any?(tag) }
|
52
|
+
project_minipage pretty_tag_name(tag), projects_in_time(projects, limit)
|
53
|
+
end&.join
|
54
|
+
end
|
55
|
+
|
56
|
+
def works_in_progress
|
57
|
+
projects = @projects.select(&:wip?)
|
58
|
+
project_minipage "Works in progress", projects if projects
|
59
|
+
end
|
60
|
+
|
61
|
+
def past_due
|
62
|
+
projects = @projects.select { |x| x.due ? x.due < Date.today : false }
|
63
|
+
project_minipage "Past due", projects if projects
|
64
|
+
end
|
65
|
+
|
66
|
+
def recurring_defer
|
67
|
+
projects = @projects.select { |x| x.last_for ? x.last_for_deferral < Date.today + 8 : false }
|
68
|
+
project_minipage "Recurring defer", projects if projects
|
69
|
+
end
|
70
|
+
|
71
|
+
def upcoming_due
|
72
|
+
projects = @projects.select { |x| x.due ? (x.due >= Date.today && x.due < Date.today + 8) : false }
|
73
|
+
project_minipage "Upcoming due", projects if projects
|
74
|
+
end
|
75
|
+
|
76
|
+
def external_commits
|
77
|
+
project_minipage("Externals", @external_commits) unless @external_commits&.empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
def event_writeins
|
81
|
+
minipage(MINIPAGE_SIZE) do
|
82
|
+
"Events:\n#{
|
83
|
+
itemize_tightlist do
|
84
|
+
7.times.map do |n|
|
85
|
+
itemline(
|
86
|
+
writein_line("4cm"),
|
87
|
+
opt: (Date.today + n).strftime("%a %m-%d")
|
88
|
+
)
|
89
|
+
end.join.concat("\n")
|
90
|
+
end
|
91
|
+
}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def pretty_tag_name(tag)
|
96
|
+
tag.capitalize.gsub("_", " ")
|
97
|
+
end
|
98
|
+
|
99
|
+
def project_minipage(thing, projects = [])
|
100
|
+
unless projects&.empty?
|
101
|
+
minipage(MINIPAGE_SIZE) do
|
102
|
+
"#{thing}:
|
103
|
+
#{itemize_tightlist do
|
104
|
+
projects&.map do |x|
|
105
|
+
itemline("#{x.name} #{x&.time_estimate}", opt: SQUARE)
|
106
|
+
end&.join("\n")
|
107
|
+
end
|
108
|
+
}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
DEFAULT_PROJECT_TIME_ESTIMATE_MIN = 120
|
114
|
+
def projects_in_time(projects, time)
|
115
|
+
projects ||= []
|
116
|
+
t = parse_time_duration(time)
|
117
|
+
p = []
|
118
|
+
projects.each do |proj|
|
119
|
+
e = proj&.time_estimate ? parse_time_duration(proj.time_estimate) : DEFAULT_PROJECT_TIME_ESTIMATE_MIN
|
120
|
+
if e <= t
|
121
|
+
p.append proj
|
122
|
+
t -= e
|
123
|
+
end
|
124
|
+
end
|
125
|
+
p
|
126
|
+
end
|
127
|
+
|
128
|
+
def week_line
|
129
|
+
"For the next week: #{Date.today} - #{Date.today + 7}\n\n"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/lib/plansheet/sheet.rb
CHANGED
data/lib/plansheet/version.rb
CHANGED
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.
|
4
|
+
version: 0.34.1
|
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-
|
11
|
+
date: 2022-09-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dc-kwalify
|
@@ -75,7 +75,11 @@ files:
|
|
75
75
|
- lib/plansheet/project/stringify.rb
|
76
76
|
- lib/plansheet/project/yaml.rb
|
77
77
|
- lib/plansheet/sheet.rb
|
78
|
+
- lib/plansheet/sheet/daily.rb
|
78
79
|
- lib/plansheet/sheet/latex.rb
|
80
|
+
- lib/plansheet/sheet/latex_utils.rb
|
81
|
+
- lib/plansheet/sheet/monthly.rb
|
82
|
+
- lib/plansheet/sheet/weekly.rb
|
79
83
|
- lib/plansheet/time.rb
|
80
84
|
- lib/plansheet/version.rb
|
81
85
|
homepage: https://dafyddcrosby.com
|