aoc_cli 0.1.3 → 0.2.0
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/.gitignore +2 -0
- data/CHANGELOG.md +18 -2
- data/Gemfile +0 -8
- data/README.md +138 -53
- data/aoc_cli.gemspec +1 -1
- data/bin/setup +1 -2
- data/lib/aoc_cli.rb +1 -1
- data/lib/aoc_cli/commands.rb +86 -69
- data/lib/aoc_cli/database.rb +75 -14
- data/lib/aoc_cli/day.rb +58 -87
- data/lib/aoc_cli/errors.rb +18 -3
- data/lib/aoc_cli/files.rb +125 -56
- data/lib/aoc_cli/help.rb +30 -9
- data/lib/aoc_cli/interface.rb +45 -28
- data/lib/aoc_cli/paths.rb +33 -30
- data/lib/aoc_cli/solve.rb +27 -35
- data/lib/aoc_cli/tables.rb +79 -79
- data/lib/aoc_cli/tools.rb +30 -6
- data/lib/aoc_cli/version.rb +1 -3
- data/lib/aoc_cli/year.rb +49 -32
- data/sample/aoc.rc +21 -0
- metadata +4 -4
- data/pkg/aoc_cli-0.1.2.gem +0 -0
data/lib/aoc_cli/database.rb
CHANGED
@@ -1,15 +1,20 @@
|
|
1
1
|
module AocCli
|
2
2
|
module Database
|
3
|
+
def self.correct(attempt:)
|
4
|
+
attempt = Attempt.new(attempt:attempt).correct
|
5
|
+
Stats::Complete.new(n:attempt.count_attempts).update
|
6
|
+
Calendar::Part.new.increment
|
7
|
+
end
|
3
8
|
class Query
|
4
9
|
require 'sqlite3'
|
5
10
|
attr_reader :db
|
6
11
|
def initialize(path:)
|
7
12
|
@db = SQLite3::Database.open(path)
|
8
13
|
end
|
9
|
-
def select(t:, cols:"*",
|
14
|
+
def select(t:, cols:"*", where:)
|
10
15
|
db.execute(
|
11
16
|
"SELECT #{cols} FROM #{t} "\
|
12
|
-
"WHERE #{
|
17
|
+
"WHERE #{where.map{|k, v| "#{k} = #{v}"}.join(" AND ")}")
|
13
18
|
end
|
14
19
|
def table(t:, cols:)
|
15
20
|
db.execute(
|
@@ -31,25 +36,24 @@ module AocCli
|
|
31
36
|
self
|
32
37
|
end
|
33
38
|
end
|
34
|
-
class
|
39
|
+
class Attempt
|
35
40
|
attr_reader :attempt, :db
|
36
41
|
def initialize(attempt:)
|
37
42
|
@attempt = attempt
|
38
|
-
@db = Query.new(path:Paths::Database
|
39
|
-
.cfg("#{attempt.user}"))
|
43
|
+
@db = Query.new(path:Paths::Database.cfg(attempt.user))
|
40
44
|
.table(t:"attempts", cols:cols)
|
41
45
|
end
|
42
46
|
def correct
|
43
47
|
db.insert(t:"attempts", val:data << 1 << 0 << 0)
|
44
48
|
self
|
45
49
|
end
|
46
|
-
def incorrect(
|
47
|
-
db.insert(t:"attempts",
|
48
|
-
|
50
|
+
def incorrect(low:, high:)
|
51
|
+
db.insert(t:"attempts",
|
52
|
+
val:data << 0 << parse_hint(low:low, high:high))
|
49
53
|
self
|
50
54
|
end
|
51
|
-
def parse_hint(
|
52
|
-
[
|
55
|
+
def parse_hint(low:, high:)
|
56
|
+
[ low ? 1 : 0, high ? 1 : 0 ]
|
53
57
|
end
|
54
58
|
def data
|
55
59
|
[ "'#{Time.now}'",
|
@@ -69,7 +73,7 @@ module AocCli
|
|
69
73
|
high: :INT }
|
70
74
|
end
|
71
75
|
def count_attempts
|
72
|
-
db.select(t:"attempts",
|
76
|
+
db.select(t:"attempts", where:where).count
|
73
77
|
end
|
74
78
|
def where
|
75
79
|
{ year:attempt.year,
|
@@ -148,12 +152,69 @@ module AocCli
|
|
148
152
|
.map{|t| t.to_i.to_s.rjust(2, "0")}.join(":")
|
149
153
|
end
|
150
154
|
def dl_time
|
151
|
-
@dl_time ||= Time
|
152
|
-
.
|
153
|
-
.select(t:"stats", cols:"dl_time", data:where)
|
155
|
+
@dl_time ||= Time.parse(db
|
156
|
+
.select(t:"stats", cols:"dl_time", where:where)
|
154
157
|
.flatten.first)
|
155
158
|
end
|
156
159
|
end
|
157
160
|
end
|
161
|
+
module Calendar
|
162
|
+
class Init
|
163
|
+
attr_reader :user, :year, :db, :stars
|
164
|
+
def initialize(u:Metafile.get(:user),
|
165
|
+
y:Metafile.get(:year),
|
166
|
+
stars:)
|
167
|
+
@user = Validate.user(u)
|
168
|
+
@year = Validate.year(y)
|
169
|
+
@stars = stars
|
170
|
+
@db = Query
|
171
|
+
.new(path:Paths::Database.cfg(user))
|
172
|
+
.table(t:"calendar", cols:cols)
|
173
|
+
end
|
174
|
+
def cols
|
175
|
+
{ year: :INT,
|
176
|
+
day: :INT,
|
177
|
+
stars: :TEXT }
|
178
|
+
end
|
179
|
+
def n_stars(day)
|
180
|
+
stars.keys.include?(day) ? stars[day] : 0
|
181
|
+
end
|
182
|
+
def day_data(day)
|
183
|
+
["'#{year}'", "'#{day}'", "'#{n_stars(day)}'"]
|
184
|
+
end
|
185
|
+
def table_exist?
|
186
|
+
db.select(t:"calendar", where:{year:"'#{year}'"}).count > 0
|
187
|
+
end
|
188
|
+
def insert
|
189
|
+
unless table_exist?
|
190
|
+
1.upto(25){|day|
|
191
|
+
db.insert(t:"calendar",
|
192
|
+
val:day_data(day))}
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
class Part
|
197
|
+
attr_reader :user, :year, :day, :db
|
198
|
+
def initialize(u:Metafile.get(:user),
|
199
|
+
y:Metafile.get(:year),
|
200
|
+
d:Metafile.get(:day))
|
201
|
+
@user = Validate.user(u)
|
202
|
+
@year = Validate.year(y)
|
203
|
+
@day = Validate.day(d)
|
204
|
+
@db = Query.new(path:Paths::Database.cfg(user))
|
205
|
+
end
|
206
|
+
def get
|
207
|
+
db.select(t:"calendar", cols:"stars", where:where)
|
208
|
+
.flatten.first.to_i + 1
|
209
|
+
end
|
210
|
+
def increment
|
211
|
+
db.update(t:"calendar", val:{stars:get}, where:where)
|
212
|
+
end
|
213
|
+
def where
|
214
|
+
{ year:"'#{year}'",
|
215
|
+
day:"'#{day}'" }
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
158
219
|
end
|
159
220
|
end
|
data/lib/aoc_cli/day.rb
CHANGED
@@ -1,12 +1,7 @@
|
|
1
1
|
module AocCli
|
2
2
|
module Day
|
3
|
-
def self.refresh
|
4
|
-
puts "- Updating puzzle...".yellow
|
5
|
-
Init.new.write
|
6
|
-
Data::Puzzle.new.write
|
7
|
-
end
|
8
3
|
class Init
|
9
|
-
attr_reader :
|
4
|
+
attr_reader :user, :year, :day, :paths
|
10
5
|
def initialize(u:Metafile.get(:user),
|
11
6
|
y:Metafile.get(:year),
|
12
7
|
d:Metafile.get(:day))
|
@@ -19,135 +14,111 @@ module AocCli
|
|
19
14
|
Dir.mkdir(Validate.day_dir(paths.day_dir))
|
20
15
|
self
|
21
16
|
end
|
22
|
-
def
|
17
|
+
def meta
|
23
18
|
File.write(paths.local(f:"meta"),
|
24
|
-
|
19
|
+
Metafile.day(u:user, y:year, d:day))
|
25
20
|
self
|
26
21
|
end
|
27
22
|
end
|
28
23
|
class Pages < Init
|
29
|
-
attr_reader :
|
24
|
+
attr_reader :files, :use_cache
|
30
25
|
def initialize(u:Metafile.get(:user),
|
31
26
|
y:Metafile.get(:year),
|
32
27
|
d:Metafile.get(:day),
|
33
|
-
f:[:Input, :Puzzle]
|
28
|
+
f:[:Input, :Puzzle],
|
29
|
+
use_cache:true)
|
34
30
|
super(u:u, y:y, d:d)
|
35
31
|
@files = f
|
32
|
+
@use_cache = use_cache
|
36
33
|
end
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
download(page:page)}
|
34
|
+
def load
|
35
|
+
files.each do |file| use_cache && cache[file] ?
|
36
|
+
copy(file:file) : download(page:file) end
|
41
37
|
end
|
42
|
-
private
|
43
38
|
def cache
|
44
|
-
@cache ||= Cache
|
45
|
-
|
39
|
+
@cache ||= Cache.new(d:day, f:files).query
|
40
|
+
end
|
41
|
+
def copy(file:)
|
42
|
+
File.write(paths.local(f:file), cache[file])
|
46
43
|
end
|
47
|
-
def download(page
|
48
|
-
|
44
|
+
def download(page:)
|
45
|
+
req = Requests.const_get(page)
|
49
46
|
.new(u:user, y:year, d:day)
|
50
|
-
.write(to:
|
51
|
-
|
52
|
-
|
53
|
-
|
47
|
+
.write(to:paths.cache_and_local(f:page))
|
48
|
+
req.init_stats if page == :Puzzle
|
49
|
+
end
|
50
|
+
end
|
51
|
+
class Cache < Pages
|
52
|
+
def initialize(u:Metafile.get(:user),
|
53
|
+
y:Metafile.get(:year),
|
54
|
+
d:Metafile.get(:day),
|
55
|
+
f:[:Input, :Puzzle])
|
56
|
+
super(u:u, y:y, d:d, f:f)
|
57
|
+
paths.create_cache
|
58
|
+
end
|
59
|
+
def query
|
60
|
+
files.map{|file| [file, read(file:file)]}.to_h
|
61
|
+
end
|
62
|
+
private
|
63
|
+
def read(file:)
|
64
|
+
File.exist?(paths.cache_path(f:file)) ?
|
65
|
+
File.read(paths.cache_path(f:file)) : nil
|
54
66
|
end
|
55
67
|
end
|
56
|
-
module
|
57
|
-
class
|
58
|
-
attr_reader :
|
68
|
+
module Requests
|
69
|
+
class Request < Init
|
70
|
+
attr_reader :data, :part
|
59
71
|
def initialize(u:Metafile.get(:user),
|
60
72
|
y:Metafile.get(:year),
|
61
73
|
d:Metafile.get(:day))
|
62
74
|
super(u:u, y:y, d:d)
|
75
|
+
@data = parse(raw:fetch)
|
63
76
|
@part = Metafile.part(d:day)
|
64
|
-
@data = parse(raw: fetch)
|
65
77
|
end
|
66
|
-
def write(to:
|
67
|
-
to.each{|path| File.write(path, data)}
|
68
|
-
self
|
78
|
+
def write(to:)
|
79
|
+
to.each{|path| File.write(path, data)}; self
|
69
80
|
end
|
70
81
|
private
|
71
82
|
def fetch
|
72
83
|
Tools::Get.new(u:user, y:year, d:day, p:page)
|
73
84
|
end
|
74
85
|
end
|
75
|
-
class Puzzle <
|
86
|
+
class Puzzle < Request
|
76
87
|
def page
|
77
88
|
:Puzzle
|
78
89
|
end
|
79
|
-
def parse(raw:)
|
90
|
+
def parse(raw:fetch)
|
80
91
|
raw.chunk(f:"<article", t:"<\/article", f_off:2)
|
81
92
|
.md
|
82
93
|
.gsub(/(?<=\])\[\]/, "")
|
83
94
|
.gsub(/\n.*<!--.*-->.*\n/, "")
|
84
95
|
end
|
85
|
-
def
|
96
|
+
def init_stats
|
86
97
|
Database::Stats::Init
|
87
98
|
.new(d:day, p:part)
|
88
|
-
.init if part < 3
|
89
|
-
|
99
|
+
.init if part < 3 && !stats_exist?
|
100
|
+
end
|
101
|
+
def stats_exist?
|
102
|
+
Database::Query
|
103
|
+
.new(path:Paths::Database.cfg(user))
|
104
|
+
.select(t:"stats",
|
105
|
+
where:{year:year, day:day, part:part})
|
106
|
+
.count > 0
|
90
107
|
end
|
91
108
|
end
|
92
|
-
class Input <
|
109
|
+
class Input < Request
|
93
110
|
def page
|
94
111
|
:Input
|
95
112
|
end
|
96
|
-
def parse(raw:)
|
113
|
+
def parse(raw:fetch)
|
97
114
|
raw.raw
|
98
115
|
end
|
99
116
|
end
|
100
117
|
end
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
d:Metafile.get(:day),
|
106
|
-
f:[:Input, :Puzzle])
|
107
|
-
super(u:u, y:y, d:d, f:f)
|
108
|
-
FileUtils.mkdir_p(paths.cache_dir) unless Dir
|
109
|
-
.exist?(paths.cache_dir)
|
110
|
-
end
|
111
|
-
def load
|
112
|
-
files.map{|f| [f, read_file(f:f)]}.to_h
|
113
|
-
end
|
114
|
-
private
|
115
|
-
def read_file(f:)
|
116
|
-
cached?(f:f) ? read(f:f) : nil
|
117
|
-
end
|
118
|
-
def cached?(f:)
|
119
|
-
File.exist?(paths.cache_path(f:f))
|
120
|
-
end
|
121
|
-
def read(f:)
|
122
|
-
File.read(paths.cache_path(f:f))
|
123
|
-
end
|
124
|
-
end
|
125
|
-
class Reddit
|
126
|
-
attr_reader :year, :day, :uniq, :browser
|
127
|
-
def initialize(y:Metafile.get(:year),
|
128
|
-
d:Metafile.get(:day),
|
129
|
-
b:false)
|
130
|
-
@year = Validate.year(y)
|
131
|
-
@day = Validate.day(d)
|
132
|
-
@uniq = Database::Query
|
133
|
-
.new(path:Paths::Database.root("reddit"))
|
134
|
-
.select(t:"'#{year}'", data:{day:"'#{day}'"})
|
135
|
-
.flatten[1]
|
136
|
-
@browser = b
|
137
|
-
end
|
138
|
-
def open
|
139
|
-
system("#{browser ? "open" : cmd} #{link}")
|
140
|
-
end
|
141
|
-
def cmd
|
142
|
-
["ttrv", "rtv"]
|
143
|
-
.map{|cli| cli unless `which #{cli}`.empty?}
|
144
|
-
.reject{|cmd| cmd.nil?}&.first || "open"
|
145
|
-
end
|
146
|
-
def link
|
147
|
-
"https://www.reddit.com/r/"\
|
148
|
-
"adventofcode/comments/#{uniq}/"\
|
149
|
-
"#{year}_day_#{day}_solutions"
|
150
|
-
end
|
118
|
+
def self.refresh(files:[:Input, :Puzzle])
|
119
|
+
puts "- Updating puzzle...".blue
|
120
|
+
Init.new.meta
|
121
|
+
Pages.new(f:files, use_cache:false).load
|
151
122
|
end
|
152
123
|
end
|
153
124
|
end
|
data/lib/aoc_cli/errors.rb
CHANGED
@@ -112,7 +112,7 @@ module AocCli
|
|
112
112
|
def message
|
113
113
|
<<~error
|
114
114
|
#{ERROR}: No session key value.
|
115
|
-
Use the #{"-k".yellow} or #{"--key".yellow} flags
|
115
|
+
Use the #{"-k".yellow} or #{"--key".yellow} flags to store the key
|
116
116
|
error
|
117
117
|
end
|
118
118
|
end
|
@@ -123,7 +123,7 @@ module AocCli
|
|
123
123
|
end
|
124
124
|
def message
|
125
125
|
<<~error
|
126
|
-
#{ERROR}: The key #{
|
126
|
+
#{ERROR}: The key #{key.yellow} already exists in your config file
|
127
127
|
error
|
128
128
|
end
|
129
129
|
end
|
@@ -160,12 +160,27 @@ module AocCli
|
|
160
160
|
error
|
161
161
|
end
|
162
162
|
end
|
163
|
-
class
|
163
|
+
class CmdNil < StandardError
|
164
164
|
def message
|
165
165
|
<<~error
|
166
166
|
#{ERROR}: Flags passed but no command specified
|
167
167
|
error
|
168
168
|
end
|
169
169
|
end
|
170
|
+
class KeyInv < StandardError
|
171
|
+
def message
|
172
|
+
<<~error
|
173
|
+
#{ERROR}: Invalid key
|
174
|
+
Double check your session key. It should start with "session="
|
175
|
+
error
|
176
|
+
end
|
177
|
+
end
|
178
|
+
class ConfigExist < StandardError
|
179
|
+
def message
|
180
|
+
<<~error
|
181
|
+
#{ERROR}: A config file already exists in #{Paths::Config.path.blue}
|
182
|
+
error
|
183
|
+
end
|
184
|
+
end
|
170
185
|
end
|
171
186
|
end
|
data/lib/aoc_cli/files.rb
CHANGED
@@ -1,52 +1,106 @@
|
|
1
1
|
module AocCli
|
2
2
|
module Files
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
3
|
+
module Config
|
4
|
+
class Tools
|
5
|
+
def self.is_set?(key:nil, val:nil)
|
6
|
+
read.split("\n").grep(/(?<!\/\/)#{key}=>#{val}/).any?
|
7
|
+
end
|
8
|
+
def self.get_all(key:)
|
9
|
+
read.scan(/(?:(?<=(?<!\/\/)#{key}=>)).*$/)
|
10
|
+
end
|
11
|
+
def self.get_line(key:)
|
12
|
+
get_all(key:key)&.first
|
13
|
+
#read.scan(/(?:(?<=(?<!\/\/)#{key}=>)).*$/)&.first
|
14
|
+
end
|
15
|
+
def self.get_bool(key:)
|
16
|
+
get_line(key:key) == "true" ? true : false
|
17
|
+
end
|
18
|
+
def self.mod_line(key:, val:)
|
19
|
+
is_set?(key:key) ?
|
20
|
+
write(f:read.gsub(/(?<=^#{key}=>).*$/, val.to_s)) :
|
21
|
+
write(f:"#{key}=>#{val}\n", m:"a")
|
22
|
+
end
|
23
|
+
def self.set_line(key:, val:)
|
18
24
|
write(f:"#{key}=>#{val}\n", m:"a")
|
19
|
-
|
20
|
-
|
21
|
-
read
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
25
|
+
end
|
26
|
+
private
|
27
|
+
def self.read
|
28
|
+
Paths::Config.create
|
29
|
+
File.read(Paths::Config.path)
|
30
|
+
end
|
31
|
+
def self.write(f:, m:"w")
|
32
|
+
Paths::Config.create
|
33
|
+
File.write(Paths::Config.path, f, mode:m)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
class Prefs < Tools
|
37
|
+
def self.default_alias
|
38
|
+
is_set?(key:"default") ? get_line(key:"default") :
|
39
|
+
is_set?(key:"cookie=>main") ? "main" :
|
40
|
+
list_aliases.first || "main"
|
41
|
+
end
|
42
|
+
def self.list_aliases
|
43
|
+
get_all(key:"cookie")&.map{|a| a.gsub(/=>.*/, "")}
|
44
|
+
end
|
45
|
+
def self.bool(key:)
|
46
|
+
is_set?(key:key) ?
|
47
|
+
get_bool(key:key) : defaults[key.to_sym]
|
48
|
+
end
|
49
|
+
def self.string(key:)
|
50
|
+
is_set?(key:key) ?
|
51
|
+
get_line(key:key) : defaults[key.to_sym]
|
52
|
+
end
|
53
|
+
private
|
54
|
+
def self.defaults
|
55
|
+
{ calendar_file:true,
|
56
|
+
ignore_md_files:true,
|
57
|
+
ignore_meta_files:true,
|
58
|
+
init_git:false,
|
59
|
+
lb_in_calendar:true,
|
60
|
+
reddit_in_browser:false,
|
61
|
+
unicode_tables:true
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
class Cookie < Tools
|
66
|
+
def self.store(user:, key:)
|
67
|
+
set_line(key:"cookie=>#{Validate.set_user(user)}",
|
68
|
+
val:Validate.set_key(key))
|
69
|
+
end
|
70
|
+
def self.key(user:)
|
71
|
+
Validate.key(get_line(key:"cookie=>#{Validate
|
72
|
+
.user(user)}"))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
class Example
|
76
|
+
def self.write
|
77
|
+
File.write(Validate.no_config, file)
|
78
|
+
end
|
79
|
+
def self.file
|
80
|
+
<<~file
|
81
|
+
//aoc-cli example config
|
82
|
+
//See the github repo for more information on configuring aoc-cli
|
83
|
+
//https://github.com/apexatoll/aoc-cli
|
84
|
+
|
85
|
+
[General]
|
86
|
+
//Print table in unicode rather than ascii
|
87
|
+
unicode_tables=>true
|
88
|
+
//Open Reddit in browser rather than use a Reddit CLI
|
89
|
+
reddit_in_browser=>false
|
90
|
+
|
91
|
+
[Initialise Year]
|
92
|
+
//Create a calendar file
|
93
|
+
calendar_file=>true
|
94
|
+
//Initialise git repo on year initialisation
|
95
|
+
init_git=>false
|
96
|
+
//Add calendar and puzzle files to gitignore
|
97
|
+
ignore_md_files=>true
|
98
|
+
//Add .meta files to gitignore
|
99
|
+
ignore_meta_files=>true
|
100
|
+
//Include leaderboard stats in calendar file
|
101
|
+
lb_in_calendar=>true
|
102
|
+
file
|
103
|
+
end
|
50
104
|
end
|
51
105
|
end
|
52
106
|
class Metafile
|
@@ -56,22 +110,13 @@ module AocCli
|
|
56
110
|
def self.type
|
57
111
|
get("dir").to_sym
|
58
112
|
end
|
59
|
-
def self.add(hash:, path:".meta")
|
60
|
-
hash.map {|k, v| "#{k}=>#{v}\n"}
|
61
|
-
.each{|l| File.write(path, l, mode:"a")}
|
62
|
-
end
|
63
113
|
def self.part(d:)
|
64
|
-
|
65
|
-
.scan(/(?<=stars=>).*$/)&.first)[d.to_s]
|
66
|
-
.to_i + 1
|
114
|
+
Database::Calendar::Part.new(d:d).get
|
67
115
|
end
|
68
116
|
private
|
69
117
|
def self.read(dir:".")
|
70
118
|
File.read("#{Validate.init(dir)}/.meta")
|
71
119
|
end
|
72
|
-
def self.root_dir
|
73
|
-
type == :ROOT ? "." : ".."
|
74
|
-
end
|
75
120
|
def self.year(u:, y:)
|
76
121
|
<<~meta
|
77
122
|
dir=>ROOT
|
@@ -89,5 +134,29 @@ module AocCli
|
|
89
134
|
meta
|
90
135
|
end
|
91
136
|
end
|
137
|
+
class Calendar
|
138
|
+
attr_reader :cal, :stats, :year
|
139
|
+
def initialize(y:Metafile.get(:year), cal:, stats:)
|
140
|
+
@year, @cal, @stats = Validate.year(y), cal, stats
|
141
|
+
end
|
142
|
+
def include_leaderboard?
|
143
|
+
Prefs.bool(key:"lb_in_calendar")
|
144
|
+
end
|
145
|
+
def title
|
146
|
+
"Year #{year}: #{stats.total_stars}/50 *"
|
147
|
+
end
|
148
|
+
def underline
|
149
|
+
"-" * (cal.data[0].to_s.length + 2)
|
150
|
+
end
|
151
|
+
def make
|
152
|
+
<<~file
|
153
|
+
#{title}
|
154
|
+
#{underline}
|
155
|
+
#{cal.data.join("\n")}\n
|
156
|
+
#{stats.data.join("\n") if stats.total_stars > 0 &&
|
157
|
+
include_leaderboard?}
|
158
|
+
file
|
159
|
+
end
|
160
|
+
end
|
92
161
|
end
|
93
162
|
end
|