plansheet 0.30.1 → 0.34.1
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 +4 -4
- data/.rubocop.yml +1 -4
- data/Gemfile +1 -2
- data/Rakefile +1 -3
- data/exe/plansheet +31 -4
- data/lib/plansheet/project/yaml.rb +10 -0
- data/lib/plansheet/project.rb +20 -21
- 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 -3
- data/Gemfile.lock +0 -108
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/.rubocop.yml
CHANGED
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"
|
@@ -29,6 +32,10 @@ parser.on(
|
|
29
32
|
"--time-roi",
|
30
33
|
"Show projects with a time return-on-investment"
|
31
34
|
)
|
35
|
+
parser.on(
|
36
|
+
"--needs-thinking",
|
37
|
+
"Show projects that need tasks, time estimates, etc (WIP)"
|
38
|
+
)
|
32
39
|
parser.on(
|
33
40
|
"--calendar",
|
34
41
|
"List of projects ordered by due date"
|
@@ -37,7 +44,6 @@ parser.on(
|
|
37
44
|
"--location_filter LOCATION",
|
38
45
|
"location filter for CLI dump (WIP)"
|
39
46
|
)
|
40
|
-
options = {}
|
41
47
|
parser.parse!(into: options)
|
42
48
|
|
43
49
|
config = Plansheet.load_config
|
@@ -45,9 +51,21 @@ pool = Plansheet::Pool.new({ projects_dir: config["projects_dir"],
|
|
45
51
|
sort_order: config["sort_order"] })
|
46
52
|
|
47
53
|
if options[:sheet] || options.empty?
|
54
|
+
# TODO: Add pdflatex command (customizable path)
|
48
55
|
require "plansheet/sheet"
|
49
56
|
FileUtils.mkdir_p config["output_dir"]
|
50
|
-
|
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
|
51
69
|
elsif options[:irb]
|
52
70
|
binding.irb # rubocop:disable Lint/Debugger
|
53
71
|
elsif options[:stats]
|
@@ -68,6 +86,15 @@ elsif options[:"time-roi"]
|
|
68
86
|
puts "time ROI payoff: #{proj.time_roi_payoff}"
|
69
87
|
puts "\n"
|
70
88
|
end
|
89
|
+
elsif options[:"needs-thinking"]
|
90
|
+
puts "Projects with no tasks"
|
91
|
+
project_arr = pool.projects.sort
|
92
|
+
project_arr.delete_if(&:dropped_or_done?)
|
93
|
+
project_arr.delete_if(&:tasks)
|
94
|
+
project_arr.each do |proj|
|
95
|
+
puts proj
|
96
|
+
puts "\n"
|
97
|
+
end
|
71
98
|
elsif options[:calendar]
|
72
99
|
# TODO: add a project filter method
|
73
100
|
project_arr = pool.projects
|
@@ -140,6 +140,16 @@ module Plansheet
|
|
140
140
|
type: seq
|
141
141
|
sequence:
|
142
142
|
- type: str
|
143
|
+
"setup_tasks":
|
144
|
+
desc: List of tasks needed before main project work begins
|
145
|
+
type: seq
|
146
|
+
sequence:
|
147
|
+
- type: str
|
148
|
+
"cleanup_tasks":
|
149
|
+
desc: List of tasks needed before main project work begins
|
150
|
+
type: seq
|
151
|
+
sequence:
|
152
|
+
- type: str
|
143
153
|
"done":
|
144
154
|
desc: List of tasks which have been completed
|
145
155
|
type: seq
|
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,
|
@@ -58,7 +61,7 @@ module Plansheet
|
|
58
61
|
day_of_week frequency last_for lead_time].freeze
|
59
62
|
DATE_PROPERTIES = %w[due defer paused_on dropped_on completed_on created_on starts_on last_done
|
60
63
|
last_reviewed].freeze
|
61
|
-
ARRAY_PROPERTIES = %w[dependencies externals urls tasks done tags].freeze
|
64
|
+
ARRAY_PROPERTIES = %w[dependencies externals urls tasks setup_tasks cleanup_tasks done tags].freeze
|
62
65
|
|
63
66
|
ALL_PROPERTIES = STRING_PROPERTIES + DATE_PROPERTIES + ARRAY_PROPERTIES
|
64
67
|
|
@@ -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
|
@@ -63,7 +63,6 @@ files:
|
|
63
63
|
- ".rubocop.yml"
|
64
64
|
- CODE_OF_CONDUCT.md
|
65
65
|
- Gemfile
|
66
|
-
- Gemfile.lock
|
67
66
|
- Guardfile
|
68
67
|
- LICENSE.txt
|
69
68
|
- README.md
|
@@ -76,7 +75,11 @@ files:
|
|
76
75
|
- lib/plansheet/project/stringify.rb
|
77
76
|
- lib/plansheet/project/yaml.rb
|
78
77
|
- lib/plansheet/sheet.rb
|
78
|
+
- lib/plansheet/sheet/daily.rb
|
79
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
|
80
83
|
- lib/plansheet/time.rb
|
81
84
|
- lib/plansheet/version.rb
|
82
85
|
homepage: https://dafyddcrosby.com
|
data/Gemfile.lock
DELETED
@@ -1,108 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
plansheet (0.30.1)
|
5
|
-
dc-kwalify (~> 1.0)
|
6
|
-
diffy (= 3.4.2)
|
7
|
-
rgl (= 0.5.8)
|
8
|
-
|
9
|
-
GEM
|
10
|
-
remote: https://rubygems.org/
|
11
|
-
specs:
|
12
|
-
ast (2.4.2)
|
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)
|
23
|
-
dc-kwalify (1.0.0)
|
24
|
-
dc-rubocop (0.0.3)
|
25
|
-
rubocop
|
26
|
-
diffy (3.4.2)
|
27
|
-
ffi (1.15.5)
|
28
|
-
formatador (1.1.0)
|
29
|
-
generator (0.0.1)
|
30
|
-
guard (2.18.0)
|
31
|
-
formatador (>= 0.2.4)
|
32
|
-
listen (>= 2.7, < 4.0)
|
33
|
-
lumberjack (>= 1.0.12, < 2.0)
|
34
|
-
nenv (~> 0.1)
|
35
|
-
notiffany (~> 0.0)
|
36
|
-
pry (>= 0.13.0)
|
37
|
-
shellany (~> 0.0)
|
38
|
-
thor (>= 0.18.1)
|
39
|
-
guard-rake (1.0.0)
|
40
|
-
guard
|
41
|
-
rake
|
42
|
-
json (2.6.2)
|
43
|
-
lazy_priority_queue (0.1.1)
|
44
|
-
listen (3.7.1)
|
45
|
-
rb-fsevent (~> 0.10, >= 0.10.3)
|
46
|
-
rb-inotify (~> 0.9, >= 0.9.10)
|
47
|
-
lumberjack (1.2.8)
|
48
|
-
method_source (1.0.0)
|
49
|
-
minitest (5.16.2)
|
50
|
-
nenv (0.3.0)
|
51
|
-
notiffany (0.1.3)
|
52
|
-
nenv (~> 0.1)
|
53
|
-
shellany (~> 0.0)
|
54
|
-
parallel (1.22.1)
|
55
|
-
parser (3.1.2.0)
|
56
|
-
ast (~> 2.4.1)
|
57
|
-
pry (0.14.1)
|
58
|
-
coderay (~> 1.1)
|
59
|
-
method_source (~> 1.0)
|
60
|
-
psych (4.0.4)
|
61
|
-
stringio
|
62
|
-
rainbow (3.1.1)
|
63
|
-
rake (13.0.6)
|
64
|
-
rb-fsevent (0.11.1)
|
65
|
-
rb-inotify (0.10.1)
|
66
|
-
ffi (~> 1.0)
|
67
|
-
rdoc (6.4.0)
|
68
|
-
psych (>= 4.0.0)
|
69
|
-
regexp_parser (2.5.0)
|
70
|
-
rexml (3.2.5)
|
71
|
-
rgl (0.5.8)
|
72
|
-
lazy_priority_queue (~> 0.1.0)
|
73
|
-
rexml (~> 3.2, >= 3.2.4)
|
74
|
-
stream (~> 0.5.3)
|
75
|
-
rubocop (1.32.0)
|
76
|
-
json (~> 2.3)
|
77
|
-
parallel (~> 1.10)
|
78
|
-
parser (>= 3.1.0.0)
|
79
|
-
rainbow (>= 2.2.2, < 4.0)
|
80
|
-
regexp_parser (>= 1.8, < 3.0)
|
81
|
-
rexml (>= 3.2.5, < 4.0)
|
82
|
-
rubocop-ast (>= 1.19.1, < 2.0)
|
83
|
-
ruby-progressbar (~> 1.7)
|
84
|
-
unicode-display_width (>= 1.4.0, < 3.0)
|
85
|
-
rubocop-ast (1.19.1)
|
86
|
-
parser (>= 3.1.1.0)
|
87
|
-
rubocop-minitest (0.20.1)
|
88
|
-
rubocop (>= 0.90, < 2.0)
|
89
|
-
rubocop-rake (0.6.0)
|
90
|
-
rubocop (~> 1.0)
|
91
|
-
ruby-progressbar (1.11.0)
|
92
|
-
shellany (0.0.1)
|
93
|
-
stream (0.5.4)
|
94
|
-
generator
|
95
|
-
stringio (3.0.2)
|
96
|
-
thor (1.2.1)
|
97
|
-
unicode-display_width (2.2.0)
|
98
|
-
|
99
|
-
PLATFORMS
|
100
|
-
x86_64-linux
|
101
|
-
|
102
|
-
DEPENDENCIES
|
103
|
-
dc-devtools
|
104
|
-
plansheet!
|
105
|
-
rdoc
|
106
|
-
|
107
|
-
BUNDLED WITH
|
108
|
-
2.3.6
|