aoc_cli 0.1.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 +7 -0
- data/.gitignore +1 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +282 -0
- data/Rakefile +3 -0
- data/aoc_cli.gemspec +30 -0
- data/bin/aoc +4 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/aoc_cli.rb +22 -0
- data/lib/aoc_cli/commands.rb +215 -0
- data/lib/aoc_cli/database.rb +159 -0
- data/lib/aoc_cli/day.rb +153 -0
- data/lib/aoc_cli/db/reddit.db +0 -0
- data/lib/aoc_cli/errors.rb +171 -0
- data/lib/aoc_cli/files.rb +93 -0
- data/lib/aoc_cli/help.rb +53 -0
- data/lib/aoc_cli/interface.rb +137 -0
- data/lib/aoc_cli/paths.rb +93 -0
- data/lib/aoc_cli/solve.rb +109 -0
- data/lib/aoc_cli/tables.rb +138 -0
- data/lib/aoc_cli/tools.rb +91 -0
- data/lib/aoc_cli/version.rb +5 -0
- data/lib/aoc_cli/year.rb +98 -0
- metadata +157 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Tables
|
3
|
+
class Attempts
|
4
|
+
require 'terminal-table'
|
5
|
+
attr_reader :user, :year, :day, :part, :db
|
6
|
+
def initialize(u:Metafile.get(:user),
|
7
|
+
y:Metafile.get(:year),
|
8
|
+
d:Metafile.get(:day),
|
9
|
+
p:Metafile.get(:part))
|
10
|
+
@user = Validate.user(u)
|
11
|
+
@year = Validate.year(y)
|
12
|
+
@day = Validate.day(d)
|
13
|
+
@part = Validate.part(p)
|
14
|
+
@db = Database::Query
|
15
|
+
.new(path:Paths::Database.cfg(user))
|
16
|
+
end
|
17
|
+
def show
|
18
|
+
puts rows.count > 0 ? table :
|
19
|
+
"You have not attempted this puzzle yet!"
|
20
|
+
end
|
21
|
+
private
|
22
|
+
def table
|
23
|
+
tab = Terminal::Table.new(
|
24
|
+
:headings => headings,
|
25
|
+
:rows => rows,
|
26
|
+
:title => title)
|
27
|
+
tab.style = {
|
28
|
+
:border => :unicode,
|
29
|
+
:alignment => :center}
|
30
|
+
tab
|
31
|
+
end
|
32
|
+
def title
|
33
|
+
"#{year} - Day #{day}:#{part}".bold
|
34
|
+
end
|
35
|
+
def headings
|
36
|
+
["Answer", "Time", "Hint"]
|
37
|
+
end
|
38
|
+
def rows
|
39
|
+
@rows ||= attempts
|
40
|
+
.map{|a| [parse_ans(a), parse_time(a), parse_hint(a)]}
|
41
|
+
end
|
42
|
+
def attempts
|
43
|
+
db.select(
|
44
|
+
t:"attempts",
|
45
|
+
cols:"time, answer, high, low, correct",
|
46
|
+
data:{year:year, day:day, part:part})
|
47
|
+
end
|
48
|
+
def parse_ans(attempt)
|
49
|
+
attempt[4] == 1 ?
|
50
|
+
attempt[1].to_s.green :
|
51
|
+
attempt[1].to_s.red
|
52
|
+
end
|
53
|
+
def parse_time(attempt)
|
54
|
+
DateTime
|
55
|
+
.strptime(attempt[0], "%Y-%m-%d %H:%M:%S %Z")
|
56
|
+
.strftime("%H:%M - %d/%m/%y")
|
57
|
+
end
|
58
|
+
def parse_hint(attempt)
|
59
|
+
attempt[2] == 1 ? "low" :
|
60
|
+
attempt[3] == 1 ? "high" : "-"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
module Stats
|
64
|
+
class Year
|
65
|
+
attr_reader :user, :year, :db
|
66
|
+
def initialize(u:Metafile.get(:user),
|
67
|
+
y:Metafile.get(:year))
|
68
|
+
@user = Validate.user(u)
|
69
|
+
@year = Validate.year(y)
|
70
|
+
@db = Database::Query
|
71
|
+
.new(path:Paths::Database.cfg(user))
|
72
|
+
end
|
73
|
+
def print
|
74
|
+
puts rows.count > 0 ? table :
|
75
|
+
"You have not completed any puzzles yet"
|
76
|
+
end
|
77
|
+
def rows
|
78
|
+
@rows ||= stats.map{|s| [s[0], s[1], s[2], s[3]]}
|
79
|
+
end
|
80
|
+
def table
|
81
|
+
tab = Terminal::Table.new(
|
82
|
+
:headings => headings,
|
83
|
+
:rows => rows,
|
84
|
+
:title => title)
|
85
|
+
tab.style = {
|
86
|
+
:border => :unicode,
|
87
|
+
:alignment => :center}
|
88
|
+
tab
|
89
|
+
end
|
90
|
+
def title
|
91
|
+
"Year #{year}"
|
92
|
+
end
|
93
|
+
def headings
|
94
|
+
["Day", "Part", "Attempts", "Time (h:m:s)"]
|
95
|
+
end
|
96
|
+
def stats
|
97
|
+
@stats ||= db.select(
|
98
|
+
t:"stats",
|
99
|
+
cols:"day, part, attempts, elapsed",
|
100
|
+
data:where)
|
101
|
+
end
|
102
|
+
def where
|
103
|
+
{year:"'#{year}'",
|
104
|
+
correct:"'1'"}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
class Day < Year
|
108
|
+
attr_reader :day
|
109
|
+
def initialize(u:Metafile.get(:user),
|
110
|
+
y:Metafile.get(:year),
|
111
|
+
d:Metafile.get(:day))
|
112
|
+
super(u:u, y:y)
|
113
|
+
@day = Validate.day(d)
|
114
|
+
end
|
115
|
+
def rows
|
116
|
+
@rows ||= stats.map{|s| [s[0], s[1], s[2]]}
|
117
|
+
end
|
118
|
+
def title
|
119
|
+
"Year #{year}: Day #{day}"
|
120
|
+
end
|
121
|
+
def headings
|
122
|
+
["Part", "Attempts", "Time (h:m:s)"]
|
123
|
+
end
|
124
|
+
def stats
|
125
|
+
@stats ||= db.select(
|
126
|
+
t:"stats",
|
127
|
+
cols:"part, attempts, elapsed",
|
128
|
+
data:where)
|
129
|
+
end
|
130
|
+
def where
|
131
|
+
{year:"'#{year}'",
|
132
|
+
day:"'#{day}'",
|
133
|
+
correct:"'1'"}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Tools
|
3
|
+
class Request
|
4
|
+
require 'curb'
|
5
|
+
attr_reader :user, :year, :day, :base, :page
|
6
|
+
def initialize(u:Metafile.get(:user),
|
7
|
+
y:Metafile.get(:year),
|
8
|
+
d:Metafile.get(:day), p:)
|
9
|
+
@user = Validate.user(u)
|
10
|
+
@year = Validate.year(y)
|
11
|
+
@day = d
|
12
|
+
@page = p
|
13
|
+
@base = "https://adventofcode.com/#{year}"
|
14
|
+
end
|
15
|
+
protected
|
16
|
+
def get
|
17
|
+
Curl.get(url){|h| h.headers['Cookie'] = cookie}
|
18
|
+
.body
|
19
|
+
end
|
20
|
+
def post(data:)
|
21
|
+
Curl.post(url, data){|h| h.headers['Cookie'] =
|
22
|
+
cookie}.body
|
23
|
+
end
|
24
|
+
private
|
25
|
+
def cookie
|
26
|
+
Files::Cookie.new(u:user).key
|
27
|
+
end
|
28
|
+
def url
|
29
|
+
case page.to_sym
|
30
|
+
when :Calendar then base
|
31
|
+
when :Stats then base + "/leaderboard/self"
|
32
|
+
when :Puzzle then base + "/day/#{day}"
|
33
|
+
when :Input then base + "/day/#{day}/input"
|
34
|
+
when :Answer then base + "/day/#{day}/answer"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
class Convert < Request
|
39
|
+
require 'pandoc-ruby'
|
40
|
+
attr_accessor :input
|
41
|
+
def array
|
42
|
+
input.split("\n")
|
43
|
+
end
|
44
|
+
def chunk(f:, t:, t_off:0, f_off:0)
|
45
|
+
pt1 = array.index {|l| l =~ /#{f}/} + t_off
|
46
|
+
pt2 = array.rindex{|l| l =~ /#{t}/} + f_off
|
47
|
+
@input = array.slice(pt1, pt2 - pt1).join("\n")
|
48
|
+
self
|
49
|
+
end
|
50
|
+
def raw
|
51
|
+
input.split("\n").join("\n")
|
52
|
+
end
|
53
|
+
def plain
|
54
|
+
PandocRuby.new(input, f: html, t: :plain).convert
|
55
|
+
end
|
56
|
+
def md
|
57
|
+
PandocRuby.new(input, {f: html, t: :gfm},
|
58
|
+
md_head, md_ref).convert
|
59
|
+
end
|
60
|
+
private
|
61
|
+
def html
|
62
|
+
"html-native_divs-native_spans"
|
63
|
+
end
|
64
|
+
def md_head
|
65
|
+
"--markdown-headings=setext"
|
66
|
+
end
|
67
|
+
def md_ref
|
68
|
+
"--reference-links"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
class Get < Convert
|
72
|
+
def initialize(u:Metafile.get(:user),
|
73
|
+
y:Metafile.get(:year),
|
74
|
+
d:nil, p:)
|
75
|
+
@input = Request
|
76
|
+
.new(u:u, y:y, d:d, p:p)
|
77
|
+
.get
|
78
|
+
end
|
79
|
+
end
|
80
|
+
class Post < Convert
|
81
|
+
def initialize(u:Metafile.get(:user),
|
82
|
+
y:Metafile.get(:year),
|
83
|
+
d:Metafile.get(:day),
|
84
|
+
p:"Answer", data:)
|
85
|
+
@input = Request
|
86
|
+
.new(u:u, y:y, d:d, p:p)
|
87
|
+
.post(data:data)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/aoc_cli/year.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
module AocCli
|
2
|
+
module Year
|
3
|
+
def self.refresh
|
4
|
+
puts "- Updating calendar...".blue
|
5
|
+
Year::Init.new.write
|
6
|
+
Year::Stars.new.write.update_meta
|
7
|
+
end
|
8
|
+
class Init
|
9
|
+
attr_reader :user, :year, :paths
|
10
|
+
def initialize(u:Files::Config.new.def_acc,
|
11
|
+
y:Metafile.get(:year), dir:".")
|
12
|
+
@user = Validate.user(u)
|
13
|
+
@year = Validate.year(y)
|
14
|
+
@paths = Paths::Year.new(u:user, y:year)
|
15
|
+
end
|
16
|
+
def write
|
17
|
+
File.write(paths.local(f:"meta"),
|
18
|
+
Metafile.year(u:user, y:year))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
class Stars < Init
|
22
|
+
attr_reader :stats, :cal
|
23
|
+
def initialize(u:Metafile.get(:user),
|
24
|
+
y:Metafile.get(:year))
|
25
|
+
super(u:u, y:y)
|
26
|
+
@stats = Data::Stats.new(u:user, y:year)
|
27
|
+
@cal = Data::Calendar.new(u:user, y:year)
|
28
|
+
.fill(stars:stats.stars)
|
29
|
+
end
|
30
|
+
def write
|
31
|
+
File.write(paths.local(f:"Stars"), file)
|
32
|
+
self
|
33
|
+
end
|
34
|
+
def update_meta
|
35
|
+
Metafile.add(path:paths.local(f:"meta"), hash:{
|
36
|
+
stars:stats.stars.to_json,
|
37
|
+
total:stats.total_stars})
|
38
|
+
end
|
39
|
+
private
|
40
|
+
def file
|
41
|
+
text = <<~file
|
42
|
+
Year #{year}: #{stats.total_stars}/50 *
|
43
|
+
#{"-" * (cal.data[0].to_s.length + 2)}
|
44
|
+
#{cal.data.join("\n")}\n
|
45
|
+
file
|
46
|
+
text += stats.data.join("\n") if stats.total_stars > 0
|
47
|
+
text
|
48
|
+
end
|
49
|
+
end
|
50
|
+
module Data
|
51
|
+
class YearObject < Init
|
52
|
+
attr_reader :data
|
53
|
+
def initialize(u:Metafile.get(:user),
|
54
|
+
y:Metafile.get(:year))
|
55
|
+
super(u:u, y:y)
|
56
|
+
@data = parse(raw: fetch)
|
57
|
+
end
|
58
|
+
private
|
59
|
+
def fetch
|
60
|
+
Tools::Get.new(u:user, y:year, p:page).plain.split("\n")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
class Calendar < YearObject
|
64
|
+
def page
|
65
|
+
:Calendar
|
66
|
+
end
|
67
|
+
def parse(raw:)
|
68
|
+
raw.drop(raw.index{|l| l =~ /\*\*/})
|
69
|
+
.map{|l| l.gsub(/\*\*/, "")}
|
70
|
+
end
|
71
|
+
def fill(stars:)
|
72
|
+
stars.each{|s, n| data.each{|l| l
|
73
|
+
.gsub!(/(^.*)\b#{s}\b.$/, "\\1#{s}\s#{"*" * n}")}}
|
74
|
+
self
|
75
|
+
end
|
76
|
+
end
|
77
|
+
class Stats < YearObject
|
78
|
+
def page
|
79
|
+
:Stats
|
80
|
+
end
|
81
|
+
def parse(raw:)
|
82
|
+
/You haven't collected/.match?(raw.to_s) ?
|
83
|
+
raw : raw.drop(raw.index{|l| l =~ /^.*Part 1/})
|
84
|
+
end
|
85
|
+
def stars
|
86
|
+
data.map{|l| l.scan(/^\s+\d+/)&.first}
|
87
|
+
.reject{|s| s == nil}
|
88
|
+
.map{|s| [s.to_i, data
|
89
|
+
.grep(/^.*#{s}.*(\-.*){3}$/)
|
90
|
+
.count == 0 ? 2 : 1]}.to_h
|
91
|
+
end
|
92
|
+
def total_stars
|
93
|
+
stars.values.reduce(:+).to_i || 0
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aoc_cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christian Welham
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-02-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: curb
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: json
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pandoc-ruby
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: terminal-table
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: A command-line interface for the Advent of Code puzzles. Features include
|
98
|
+
downloading puzzles and inputs, solving puzzles and tracking year progress from
|
99
|
+
within the terminal
|
100
|
+
email:
|
101
|
+
- welhamm@gmail.com
|
102
|
+
executables:
|
103
|
+
- aoc
|
104
|
+
extensions: []
|
105
|
+
extra_rdoc_files: []
|
106
|
+
files:
|
107
|
+
- ".gitignore"
|
108
|
+
- Gemfile
|
109
|
+
- LICENSE.txt
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- aoc_cli.gemspec
|
113
|
+
- bin/aoc
|
114
|
+
- bin/console
|
115
|
+
- bin/setup
|
116
|
+
- lib/aoc_cli.rb
|
117
|
+
- lib/aoc_cli/commands.rb
|
118
|
+
- lib/aoc_cli/database.rb
|
119
|
+
- lib/aoc_cli/day.rb
|
120
|
+
- lib/aoc_cli/db/reddit.db
|
121
|
+
- lib/aoc_cli/errors.rb
|
122
|
+
- lib/aoc_cli/files.rb
|
123
|
+
- lib/aoc_cli/help.rb
|
124
|
+
- lib/aoc_cli/interface.rb
|
125
|
+
- lib/aoc_cli/paths.rb
|
126
|
+
- lib/aoc_cli/solve.rb
|
127
|
+
- lib/aoc_cli/tables.rb
|
128
|
+
- lib/aoc_cli/tools.rb
|
129
|
+
- lib/aoc_cli/version.rb
|
130
|
+
- lib/aoc_cli/year.rb
|
131
|
+
homepage: https://github.com/apexatoll/aoc-cli
|
132
|
+
licenses:
|
133
|
+
- MIT
|
134
|
+
metadata:
|
135
|
+
homepage_uri: https://github.com/apexatoll/aoc-cli
|
136
|
+
source_code_uri: https://github.com/apexatoll/aoc-cli
|
137
|
+
changelog_uri: https://github.com/apexatoll/aoc-cli/changelog.md
|
138
|
+
post_install_message:
|
139
|
+
rdoc_options: []
|
140
|
+
require_paths:
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: 2.3.0
|
147
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
requirements: []
|
153
|
+
rubygems_version: 3.2.3
|
154
|
+
signing_key:
|
155
|
+
specification_version: 4
|
156
|
+
summary: A command-line interface for the Advent of Code puzzles
|
157
|
+
test_files: []
|