plansheet 0.2.0 → 0.5.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 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +1 -1
- data/examples/backpack.yml +10 -0
- data/exe/plansheet +34 -72
- data/lib/plansheet/project.rb +152 -0
- data/lib/plansheet/sheet.rb +52 -0
- data/lib/plansheet/version.rb +1 -1
- data/lib/plansheet.rb +29 -1
- metadata +6 -5
- data/exe/plansheet-sorter +0 -32
- data/project.schema.yaml +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3546f42fdd342ea128be90bff2e19b7bcb5c0910990e2132b89b87cbf6a1f776
|
4
|
+
data.tar.gz: 2ea5d505262832e19e77a63e7aa7c59c9fff0b98ddc3ee8218c68b010f4768eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 335cb1a3e74ab82a1441ec034099427dc0caabaad8a87c1f2620c7143d1fd4772023fe089a7305063fb12a96d169e44dd0b01f8f20437f45bdfa945b03aa2422
|
7
|
+
data.tar.gz: 5d618d61c6a07d8e32e46cfb4dba5df0264370f01d8b4ecef4c68f9fdcf6fcf600ca33bda0a8918e63992434e2a009e4d836f1f838851e0ee94de53128fe1ab2
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -0,0 +1,10 @@
|
|
1
|
+
- project: Add snazzy patches to backpack
|
2
|
+
status: planning
|
3
|
+
tasks:
|
4
|
+
- gather patches around the house in one spot
|
5
|
+
- determine which patches to put on
|
6
|
+
- find thread that matches patch colors
|
7
|
+
- plug in sewing machine
|
8
|
+
- put thread onto bobbin
|
9
|
+
- sew patches onto backpack
|
10
|
+
|
data/exe/plansheet
CHANGED
@@ -1,77 +1,39 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
"
|
14
|
-
"
|
15
|
-
|
16
|
-
|
17
|
-
"
|
18
|
-
"
|
19
|
-
|
20
|
-
}
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
"planning"
|
39
|
-
else
|
40
|
-
"idea"
|
41
|
-
end
|
4
|
+
require "plansheet"
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
parser = OptionParser.new
|
8
|
+
parser.on(
|
9
|
+
"--sheet",
|
10
|
+
"Generates MD/LaTeX project PDF"
|
11
|
+
)
|
12
|
+
parser.on(
|
13
|
+
"--sort",
|
14
|
+
"Sort project files"
|
15
|
+
)
|
16
|
+
parser.on(
|
17
|
+
"--cli",
|
18
|
+
"CLI dump of projects (WIP)"
|
19
|
+
)
|
20
|
+
options = {}
|
21
|
+
parser.parse!(into: options)
|
22
|
+
|
23
|
+
config = Plansheet.load_config
|
24
|
+
|
25
|
+
if options[:sheet] || options.empty?
|
26
|
+
project_arr = Plansheet.load_projects_dir config["projects_dir"]
|
27
|
+
|
28
|
+
Dir.mkdir config["output_dir"] unless Dir.exist? config["output_dir"]
|
29
|
+
|
30
|
+
Plansheet::Sheet.new("#{config["output_dir"]}/projects.md", project_arr)
|
31
|
+
elsif options[:sort]
|
32
|
+
Plansheet.resort_projects_in_dir config["projects_dir"]
|
33
|
+
elsif options[:cli]
|
34
|
+
project_arr = Plansheet.load_projects_dir config["projects_dir"]
|
35
|
+
project_arr.each do |proj|
|
36
|
+
puts proj
|
37
|
+
puts "\n"
|
42
38
|
end
|
43
39
|
end
|
44
|
-
|
45
|
-
sorted_hash = project_hash.sort_by { |_, v| status_priority[v[:status]] }
|
46
|
-
|
47
|
-
def project_minipage(proj)
|
48
|
-
p proj
|
49
|
-
str = String.new
|
50
|
-
str << "\\begin{minipage}{5cm}\n"
|
51
|
-
str << "#{proj[:name]} - #{proj[:status]} \\\\\n"
|
52
|
-
proj[:tasks]&.each do |t|
|
53
|
-
str << "$\\square$ #{t} \\\\\n"
|
54
|
-
end
|
55
|
-
str << "\\end{minipage}\n"
|
56
|
-
str
|
57
|
-
end
|
58
|
-
|
59
|
-
require "date"
|
60
|
-
projects_str = String.new
|
61
|
-
projects_str << <<~FRONTMATTER
|
62
|
-
---
|
63
|
-
geometry: margin=3cm
|
64
|
-
---
|
65
|
-
\\thispagestyle{empty}
|
66
|
-
|
67
|
-
# Date: #{Date.today}
|
68
|
-
FRONTMATTER
|
69
|
-
|
70
|
-
sorted_hash.first(30).each do |_, p|
|
71
|
-
projects_str << project_minipage(p)
|
72
|
-
end
|
73
|
-
|
74
|
-
Dir.mkdir config["output_dir"] unless Dir.exist? config["output_dir"]
|
75
|
-
f = File.open("#{config["output_dir"]}/projects.md", "w")
|
76
|
-
f.write(projects_str)
|
77
|
-
f.close
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
module Plansheet
|
6
|
+
PROJECT_STATUS_PRIORITY = {
|
7
|
+
"wip" => 1,
|
8
|
+
"ready" => 2,
|
9
|
+
"blocked" => 3,
|
10
|
+
"planning" => 4,
|
11
|
+
"idea" => 5,
|
12
|
+
"dropped" => 6,
|
13
|
+
"done" => 7
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
# Once there's some stability in plansheet and dc-kwalify, will pre-load this
|
17
|
+
# to save the later YAML.load
|
18
|
+
PROJECT_YAML_SCHEMA = <<~YAML
|
19
|
+
desc: dc-tasks project schema
|
20
|
+
type: seq
|
21
|
+
sequence:
|
22
|
+
- type: map
|
23
|
+
mapping:
|
24
|
+
"project":
|
25
|
+
desc: Project name
|
26
|
+
type: str
|
27
|
+
required: yes
|
28
|
+
"status":
|
29
|
+
desc: The current status of the project
|
30
|
+
type: str
|
31
|
+
enum:
|
32
|
+
- wip # project is a work-in-progress
|
33
|
+
- ready # project is fully scoped, ready to go
|
34
|
+
- blocked # project is blocked, but otherwise ready/wip
|
35
|
+
- planning # project in planning phase
|
36
|
+
- idea # project is little more than an idea
|
37
|
+
- dropped # project has been explicitly dropped, but
|
38
|
+
# want to keep around for reference, etc
|
39
|
+
- done # project is finished, but want to keep around
|
40
|
+
# for reference, etc.
|
41
|
+
"priority":
|
42
|
+
desc: Project priority (not currently implemented)
|
43
|
+
type: str
|
44
|
+
enum:
|
45
|
+
- high
|
46
|
+
- low
|
47
|
+
"location":
|
48
|
+
desc: Location (not currently implemented)
|
49
|
+
type: str
|
50
|
+
"tasks":
|
51
|
+
desc: List of tasks to do
|
52
|
+
type: seq
|
53
|
+
sequence:
|
54
|
+
- type: str
|
55
|
+
"done":
|
56
|
+
desc: List of tasks which have been completed
|
57
|
+
type: seq
|
58
|
+
sequence:
|
59
|
+
- type: str
|
60
|
+
"notes":
|
61
|
+
desc: Free-form notes string
|
62
|
+
type: str
|
63
|
+
YAML
|
64
|
+
PROJECT_SCHEMA = YAML.safe_load(PROJECT_YAML_SCHEMA)
|
65
|
+
class Project
|
66
|
+
attr_reader :name, :tasks, :done, :notes, :location
|
67
|
+
|
68
|
+
def initialize(options)
|
69
|
+
@name = options["project"]
|
70
|
+
|
71
|
+
@tasks = options["tasks"] || []
|
72
|
+
@done = options["done"] || []
|
73
|
+
|
74
|
+
@notes = options["notes"] if options["notes"]
|
75
|
+
@location = options["location"] if options["location"]
|
76
|
+
@status = options["status"] if options["status"]
|
77
|
+
end
|
78
|
+
|
79
|
+
def status
|
80
|
+
return @status if @status
|
81
|
+
|
82
|
+
if @tasks.count.positive?
|
83
|
+
if @done.count.positive?
|
84
|
+
"wip"
|
85
|
+
else
|
86
|
+
"planning"
|
87
|
+
end
|
88
|
+
else
|
89
|
+
"idea"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_s
|
94
|
+
str = String.new
|
95
|
+
str << "# #{@name}\n"
|
96
|
+
str << "status: #{status}\n"
|
97
|
+
str << "notes: #{notes}\n" unless @notes.nil?
|
98
|
+
str << "location: #{location}\n" unless @location.nil?
|
99
|
+
str << "tasks:\n" unless @tasks.empty?
|
100
|
+
@tasks.each do |t|
|
101
|
+
str << "- #{t}\n"
|
102
|
+
end
|
103
|
+
str << "done:\n" unless @done.empty?
|
104
|
+
@done.each do |d|
|
105
|
+
str << "- #{d}\n"
|
106
|
+
end
|
107
|
+
str
|
108
|
+
end
|
109
|
+
def to_h
|
110
|
+
h = { "project" => @name }
|
111
|
+
h["status"] = status unless status == "idea"
|
112
|
+
h["notes"] = @notes unless @notes.nil?
|
113
|
+
h["location"] = @location unless @location.nil?
|
114
|
+
h["tasks"] = @tasks unless @tasks.empty?
|
115
|
+
h["done"] = @done unless @done.empty?
|
116
|
+
h
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class ProjectYAMLFile
|
121
|
+
attr_reader :projects
|
122
|
+
|
123
|
+
def initialize(path)
|
124
|
+
@path = path
|
125
|
+
# TODO: this won't GC, inline validation instead?
|
126
|
+
@raw = YAML.load_file(path)
|
127
|
+
validate_schema
|
128
|
+
@projects = @raw.map { |proj| Project.new proj }
|
129
|
+
end
|
130
|
+
|
131
|
+
def validate_schema
|
132
|
+
validator = Kwalify::Validator.new(Plansheet::PROJECT_SCHEMA)
|
133
|
+
errors = validator.validate(@raw)
|
134
|
+
# Check YAML validity
|
135
|
+
if errors && !errors.empty?
|
136
|
+
$stderr.write "Schema errors in #{l}\n"
|
137
|
+
errors.each { |err| puts "- [#{err.path}] #{err.message}" }
|
138
|
+
abort
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def sort!
|
143
|
+
@projects.sort_by! do |project|
|
144
|
+
Plansheet::PROJECT_STATUS_PRIORITY[project.status]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def yaml_dump
|
149
|
+
YAML.dump(@projects.map(&:to_h))
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
module Plansheet
|
5
|
+
# The Sheet class constructs a Markdown/LaTeX file for use with pandoc
|
6
|
+
class Sheet
|
7
|
+
def initialize(output_file, project_arr)
|
8
|
+
sorted_arr = project_arr.sort_by do |p|
|
9
|
+
Plansheet::PROJECT_STATUS_PRIORITY[p.status]
|
10
|
+
end
|
11
|
+
|
12
|
+
projects_str = String.new
|
13
|
+
projects_str << sheet_header
|
14
|
+
|
15
|
+
sorted_arr.first(60).each do |p|
|
16
|
+
projects_str << project_minipage(p)
|
17
|
+
end
|
18
|
+
puts "Writing to #{output_file}"
|
19
|
+
File.write(output_file, projects_str)
|
20
|
+
end
|
21
|
+
|
22
|
+
def sheet_header
|
23
|
+
<<~FRONTMATTER
|
24
|
+
---
|
25
|
+
geometry: margin=1.5cm
|
26
|
+
---
|
27
|
+
\\thispagestyle{empty}
|
28
|
+
|
29
|
+
# Date: #{Date.today}
|
30
|
+
FRONTMATTER
|
31
|
+
end
|
32
|
+
|
33
|
+
def project_minipage(proj)
|
34
|
+
str = String.new
|
35
|
+
str << "\\begin{minipage}{4.5cm}\n"
|
36
|
+
str << project_header(proj)
|
37
|
+
proj.tasks.each do |t|
|
38
|
+
str << "$\\square$ #{t} \\\\\n"
|
39
|
+
end
|
40
|
+
str << "\\end{minipage}\n"
|
41
|
+
str
|
42
|
+
end
|
43
|
+
|
44
|
+
def project_header(proj)
|
45
|
+
str = String.new
|
46
|
+
str << "#{proj.name} - #{proj.status}"
|
47
|
+
str << " - #{proj.location}" if proj.location
|
48
|
+
str << " \\\\\n"
|
49
|
+
str
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/plansheet/version.rb
CHANGED
data/lib/plansheet.rb
CHANGED
@@ -1,8 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "plansheet/version"
|
4
|
+
require_relative "plansheet/project"
|
5
|
+
require_relative "plansheet/sheet"
|
6
|
+
require "yaml"
|
7
|
+
require "kwalify"
|
4
8
|
|
5
9
|
module Plansheet
|
6
10
|
class Error < StandardError; end
|
7
|
-
|
11
|
+
|
12
|
+
def self.load_config
|
13
|
+
YAML.load_file "#{Dir.home}/.plansheet.yml"
|
14
|
+
rescue StandardError
|
15
|
+
abort "unable to load plansheet config file"
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.resort_projects_in_dir(dir)
|
19
|
+
project_files = Dir.glob("#{dir}/*yml")
|
20
|
+
project_files.each do |f|
|
21
|
+
pyf = ProjectYAMLFile.new(f)
|
22
|
+
pyf.sort!
|
23
|
+
File.write(f, pyf.yaml_dump)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.load_projects_dir(dir)
|
28
|
+
project_arr = []
|
29
|
+
projects = Dir.glob("*yml", base: dir)
|
30
|
+
projects.each do |l|
|
31
|
+
project_arr << ProjectYAMLFile.new(File.join(dir, l)).projects
|
32
|
+
end
|
33
|
+
|
34
|
+
project_arr.flatten!
|
35
|
+
end
|
8
36
|
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.
|
4
|
+
version: 0.5.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-05-
|
11
|
+
date: 2022-05-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dc-kwalify
|
@@ -29,7 +29,6 @@ email:
|
|
29
29
|
- dave@dafyddcrosby.com
|
30
30
|
executables:
|
31
31
|
- plansheet
|
32
|
-
- plansheet-sorter
|
33
32
|
extensions: []
|
34
33
|
extra_rdoc_files: []
|
35
34
|
files:
|
@@ -40,16 +39,18 @@ files:
|
|
40
39
|
- LICENSE.txt
|
41
40
|
- README.md
|
42
41
|
- Rakefile
|
42
|
+
- examples/backpack.yml
|
43
43
|
- exe/plansheet
|
44
|
-
- exe/plansheet-sorter
|
45
44
|
- lib/plansheet.rb
|
45
|
+
- lib/plansheet/project.rb
|
46
|
+
- lib/plansheet/sheet.rb
|
46
47
|
- lib/plansheet/version.rb
|
47
|
-
- project.schema.yaml
|
48
48
|
homepage: https://dafyddcrosby.com
|
49
49
|
licenses:
|
50
50
|
- MIT
|
51
51
|
metadata:
|
52
52
|
homepage_uri: https://dafyddcrosby.com
|
53
|
+
rubygems_mfa_required: 'true'
|
53
54
|
post_install_message:
|
54
55
|
rdoc_options: []
|
55
56
|
require_paths:
|
data/exe/plansheet-sorter
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
# An ugly script to sort projects by status (and eventually priority)
|
5
|
-
|
6
|
-
require "yaml"
|
7
|
-
|
8
|
-
status_priority = {
|
9
|
-
"wip" => 1,
|
10
|
-
"ready" => 2,
|
11
|
-
"blocked" => 3,
|
12
|
-
"planning" => 4,
|
13
|
-
"idea" => 5,
|
14
|
-
"dropped" => 6,
|
15
|
-
"done" => 7
|
16
|
-
}
|
17
|
-
|
18
|
-
def project_priority(project)
|
19
|
-
if project["status"]
|
20
|
-
project["status"]
|
21
|
-
elsif project["tasks"]
|
22
|
-
"planning"
|
23
|
-
else
|
24
|
-
"idea"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
filename = ARGV.first
|
29
|
-
contents = YAML.load_file(filename)
|
30
|
-
sorted_hash = contents.sort_by { |project| status_priority[project_priority project] }
|
31
|
-
|
32
|
-
File.write(filename, YAML.dump(sorted_hash))
|
data/project.schema.yaml
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
desc: dc-tasks project schema
|
2
|
-
type: seq
|
3
|
-
sequence:
|
4
|
-
- type: map
|
5
|
-
mapping:
|
6
|
-
"project":
|
7
|
-
desc: Project name
|
8
|
-
type: str
|
9
|
-
required: yes
|
10
|
-
"status":
|
11
|
-
desc: The current status of the project
|
12
|
-
type: str
|
13
|
-
enum:
|
14
|
-
- wip # project is a work-in-progress
|
15
|
-
- ready # project is fully scoped, ready to go
|
16
|
-
- blocked # project is blocked, but otherwise ready/wip
|
17
|
-
- planning # project in planning phase
|
18
|
-
- idea # project is little more than an idea
|
19
|
-
- dropped # project has been explicitly dropped, but
|
20
|
-
# want to keep around for reference, etc
|
21
|
-
- done # project is finished, but want to keep around
|
22
|
-
# for reference, etc.
|
23
|
-
"priority":
|
24
|
-
desc: Project priority (not currently implemented)
|
25
|
-
type: str
|
26
|
-
enum:
|
27
|
-
- high
|
28
|
-
- low
|
29
|
-
"tasks":
|
30
|
-
desc: List of tasks to do
|
31
|
-
type: seq
|
32
|
-
sequence:
|
33
|
-
- type: str
|
34
|
-
"done":
|
35
|
-
desc: List of tasks which have been completed
|
36
|
-
type: seq
|
37
|
-
sequence:
|
38
|
-
- type: str
|
39
|
-
"notes":
|
40
|
-
desc: Free-form notes string
|
41
|
-
type: str
|