aoc_cli 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9fd67606e3738d60e1a45a761ffd1bf6dec09520ff84806f2229dd10f69eb6b3
4
+ data.tar.gz: '09d5c4d55577546d743f0ea4aafee798303a9807851af02c73903d24a1be3ffb'
5
+ SHA512:
6
+ metadata.gz: bc10df6bdf33971314fedf6511fe85d0cf871bb61af2c2828218991bdcf0fa83ac6adec3027e884c88af4a80c9bcfeba3740e8379066557f0f85b040927cc41e
7
+ data.tar.gz: 4f18b284284dc1c537f242860a07942cdc8ada757bf85f768d478cfd3a43c487986297d54e1a873b73622022ff405a1ddc5488b715f6b6a62327d95fe10248e4
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ tags
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in aoc_cli.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+ #gem "pandoc-ruby"
10
+ #gem "curb"
11
+ #gem "colorize"
12
+ #gem "rspec"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Christian Welham
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,282 @@
1
+ # `Advent of Code - cli`
2
+
3
+ ![](https://github.com/apexatoll/aoc-files/blob/master/demo2.gif)
4
+
5
+ A command-line interface to interact with Advent of Code puzzles, built in Ruby.
6
+
7
+
8
+ ## Main Features
9
+ - Download puzzles as markdown and raw inputs directly from the command line
10
+ - Submit answers for puzzles and receive feedback
11
+ - Track progress through your calendar file which is automatically updated as you progress
12
+ - View data about how you answer puzzles, for example your previous attempts, how long it takes to solve a puzzle successfully and how many attempts it took you
13
+ - Inputs and puzzles are cached locally to prevent strain on AoC server
14
+ - Hot keys to open solution megathreads in Reddit if you get stuck
15
+ - Support for multiple AoC accounts by use of session-key aliases
16
+
17
+
18
+ ## Installation
19
+
20
+ Install gem
21
+
22
+ ```bash
23
+ $ gem install aoc_cli`
24
+ ```
25
+
26
+ Manual install
27
+
28
+ ```bash
29
+ $ git clone "https://github.com/apexatoll/aoc-cli"
30
+ $ cd aoc-cli
31
+ $ rake build install
32
+ ```
33
+
34
+ ## Setup
35
+
36
+ ### Finding your Session Key
37
+
38
+ To use aoc-cli you must first find and store your unique session cookie. To find this, log into the Advent of Code website as usual and load any page.
39
+
40
+ Open developer tools and open the network tab. Refresh the page and you should see a file that contains your session cookie.
41
+
42
+ It will contain a field that looks something like:
43
+
44
+ ```
45
+ cookie: session=ALPHANUMERIC STRING
46
+ ```
47
+
48
+ This is unique to your account, and allows aoc-cli to interact with the AoC server - do not share this!
49
+
50
+
51
+ ### Storing your Session Key
52
+
53
+ - Copy the session key (including "session=") to the clipboard and open your terminal
54
+ - Run the following command
55
+
56
+ ```bash
57
+ aoc -k "your key"
58
+
59
+ ```
60
+
61
+ - AoC-cli stores this key under the alias 'main' by default.
62
+ - To store the key under a different alias use the `-u` or `--user` flags followed by the desired alias.
63
+ - Session keys are stored in the aoc config file located at `~/.config/aoc-cli/aoc.rc`.
64
+
65
+ - Keys can be stored manually in your config file using the format:
66
+
67
+ ```bash
68
+ cookie=>$alias=>$key
69
+ ```
70
+
71
+ For example:
72
+
73
+ ```bash
74
+ cookie=>account2=>1234abc
75
+ ```
76
+
77
+ ### Default Alias
78
+
79
+ aoc-cli allows for multiple keys to be stored within the config file.
80
+
81
+ By default, the key under the "main" alias is used when initialising year directories and another alias is not specified
82
+
83
+ To see which alias is currently deafult run `aoc -U` or `aoc --default`
84
+
85
+ - You can update the default alias by running
86
+
87
+ ```bash
88
+ aoc -U $new_default_alias
89
+ ```
90
+
91
+ For example:
92
+
93
+ ```bash
94
+ aoc -U account2
95
+ ```
96
+
97
+ Alternatively you could add the following line to your config file
98
+
99
+ ```bash
100
+ default=>account2
101
+ ```
102
+
103
+ ## Usage
104
+
105
+ aoc-cli is designed to run in a file-tree generated by the interface.
106
+
107
+ There are two types of directories
108
+ 1. Year directories
109
+ - These contain your calendar and all day subdirectories
110
+ 2. Day subdirectories
111
+ - These contain the puzzles, your input and your code
112
+
113
+
114
+ ## Initialising the Year
115
+
116
+ To begin using the cli you must first initialise the year directory.
117
+
118
+ aoc-cli will intialise within your current working directory, so first create a directory for the year and cd into it
119
+
120
+ ```bash
121
+ mkdir 2020
122
+ cd 2020
123
+ ```
124
+ - Initialise the year directory using the command
125
+
126
+ ```bash
127
+ aoc -y 2020
128
+ ```
129
+
130
+ This will set the session key for the year directory to the default alias. To use a different AoC account run:
131
+
132
+ ```bash
133
+ aoc -y 2020 -u $alias
134
+ ```
135
+
136
+ This command will create necessary metafiles, download the year calendar and fill it with your current progress (if any). This is stored in a markdown file in the year directory.
137
+
138
+ By default, your stats in the leaderboard are also added to this file. To turn this off add the line `include_leaderboard=>false` to your config file.
139
+
140
+
141
+ ## Initialising a Day
142
+
143
+ You can now get day data!
144
+
145
+ To fetch puzzle instructions and inputs you can run the following command from within the year directory
146
+
147
+ ```bash
148
+ aoc -d $day_number
149
+ ```
150
+
151
+ This command performs the following actions
152
+
153
+ - Creates a day subdirectory
154
+ - Fetches the puzzle as markdown
155
+ - Fetches raw puzzle input
156
+ - The time you downloaded the puzzle is logged
157
+
158
+ All puzzles and inputs are cached in aoc-cli on a per-user basis. This means that if you have previously initialised this day under this alias before, your puzzle and input will be transferred from the cache locally rather than downloading from the aoc server.
159
+
160
+
161
+ ## Solving Puzzles
162
+
163
+ From the day subdirectory you can attempt to solve puzzles by running the command
164
+
165
+ ```bash
166
+ aoc -s $answer
167
+ ```
168
+
169
+ You will then receive one of three responses
170
+
171
+ 1. The answer is correct
172
+ 2. The answer is not correct
173
+ 3. You have time to wait before submitting an answer
174
+
175
+ If your answer is correct, aoc-cli will automatically update the puzzle instructions and your calendar file. Additionally aoc-cli will calculate how long and how many attempts it took to solve the puzzle (see the Tables section)
176
+
177
+ Incorrect attempts will be logged along with any hint as to whether your answer was too high or too low.
178
+
179
+ If you have sent multiple incorrect attempts AoC will ask you to wait before trying again - be patient!
180
+
181
+
182
+ ## Tables
183
+
184
+ aoc-cli uses a local database to store information about your progress with puzzles. There are two main databases of use:
185
+
186
+ 1. Your attempts
187
+ 2. Your stats
188
+
189
+
190
+ ### Attempts
191
+
192
+ Previous attempts for a puzzle can be viewed from the day directory by the use of the command
193
+
194
+ ```bash
195
+ aoc -a
196
+ ```
197
+
198
+ To specify which attempts to show from outside the day subdirectory you can use the command
199
+
200
+ ```bash
201
+ aoc -a -u $user_alias -Y $year -D $day -p $part
202
+ ```
203
+
204
+ Data is shown in a formatted table with incorrect attempts shown in red and the correct answer in green.
205
+
206
+ ![](https://github.com/apexatoll/aoc-files/blob/master/attempts.png)
207
+
208
+ ### Stats
209
+
210
+ aoc-cli also tracks data related to your performance in puzzles, namely:
211
+ - The time taken to complete a puzzle
212
+ - How many attempts it took
213
+
214
+ To view this, run the following command from the day subdirectory
215
+
216
+ ```bash
217
+ aoc -S
218
+ ```
219
+
220
+ To view the stats for the year as a whole run the same command from the year directory. Flags can be also be added manually for showing data for other users, years and days in a similar way to that of the attempts table
221
+
222
+ ![](https://github.com/apexatoll/aoc-files/blob/master/stats.png)
223
+
224
+ ## Reddit Integration
225
+
226
+ You can run the command `aoc -R` from the day subdirectory, or by manual flags to open the solution megathread for the specified day in Reddit
227
+
228
+ If one is installed, aoc-cli will default to opening the thread within a reddit-clu such as RTV or TTRV. If one isn't found however, the thread will be opened within your default browser.
229
+
230
+ To always open megathread in your browser run
231
+
232
+ ```bash
233
+ aoc -B true
234
+ ```
235
+
236
+ ## Vim Integration
237
+
238
+ if you use vim you may wish to set a keybinding to automatically run your code and submit it for validation once you have solved a puzzle.
239
+
240
+ Using Ruby as an example, you could add the following line to your .vimrc:
241
+
242
+ ```vim
243
+ autocmd filetype ruby nmap <silent><leader>ac :! aoc -s $(ruby %)<CR>
244
+ ```
245
+
246
+ Executing leader + ac would then run your program and send the answer to the server for verification (as long as your program only outputs an answer and no other text).
247
+
248
+ Do not send endless attempts - only send when you are comfortable with your answer!
249
+
250
+
251
+ ## All Flags
252
+
253
+ | Flag Short | Flag Long | Action |
254
+ |------------|----------------|-------------------------------------------|
255
+ | `-a` | `--attempts` | Print attempts table |
256
+ | `-b` | `--in-browser` | Open reddit in browser |
257
+ | `-d` | `--init-day` | Initialise day subdirectory |
258
+ | `-h` | `--help` | Show help screen |
259
+ | `-k` | `--key` | Store session key |
260
+ | `-p` | `--part` | Specify part for aoc command |
261
+ | `-r` | `--refresh` | Refresh calendar (year dir) |
262
+ | | | Refresh puzzle (day subdir) |
263
+ | `-s` | `--solve` | Solve puzzle |
264
+ | `-u` | `--user` | Specify alias for aoc command |
265
+ | `-y` | `--init-year` | Initialise year directory |
266
+ | `-B` | `--browser` | View reddit browser setting (no argument) |
267
+ | | | Set reddit browser (with argument) |
268
+ | `-D` | `--day` | Specify day for aoc command |
269
+ | `-R` | `--reddit` | Open solution megathread |
270
+ | `-S` | `--stats` | Print stats table |
271
+ | `-U` | `--default` | View default alias (no argument) |
272
+ | | | Set default alias (with argument) |
273
+ | `-Y` | `--year` | Specify year for aoc command |
274
+
275
+
276
+ ## Acknowledgments
277
+
278
+ I am in no way affiliated with AoC, but I would like to take this opportunity to thank the creator Eric Wastl for the taking the time and great effort to produce these fantastic puzzles!
279
+
280
+ Please note that requests are hard-coded to be throttled to a maximum of 1 HTTP request per 5 seconds. This is to ensure that the AoC server is not overloaded with requests. Please do not try and change this - it is to protect the server!
281
+
282
+ Please let me know if there are any bugs or issues with the cli within the issues section - I will try to address them as quickly as I can
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ require "bundler/gem_tasks"
3
+ task default: %i[]
data/aoc_cli.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ require_relative "lib/aoc_cli/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "aoc_cli"
5
+ spec.version = AocCli::VERSION
6
+ spec.authors = ["Christian Welham"]
7
+ spec.email = ["welhamm@gmail.com"]
8
+ spec.summary = "A command-line interface for the Advent of Code puzzles"
9
+ spec.description = "A command-line interface for the Advent of Code puzzles. Features include downloading puzzles and inputs, solving puzzles and tracking year progress from within the terminal"
10
+ spec.homepage = "https://github.com/apexatoll/aoc-cli"
11
+ spec.license = "MIT"
12
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
13
+ spec.metadata["homepage_uri"] = spec.homepage
14
+ spec.metadata["source_code_uri"] = "https://github.com/apexatoll/aoc-cli"
15
+ spec.metadata["changelog_uri"] = "https://github.com/apexatoll/aoc-cli/changelog.md"
16
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
17
+ `git ls-files -z`
18
+ .split("\x0")
19
+ .reject{|f| f.match(%r{\A(?:test|spec|features)/}) }
20
+ end
21
+ spec.bindir = "bin"
22
+ spec.executables << 'aoc'
23
+ spec.require_paths = ["lib"]
24
+ spec.add_dependency("colorize")
25
+ spec.add_dependency("curb")
26
+ spec.add_dependency("json")
27
+ spec.add_dependency("pandoc-ruby")
28
+ spec.add_dependency("sqlite3")
29
+ spec.add_dependency("terminal-table")
30
+ end
data/bin/aoc ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/aoc_cli.rb'
4
+ AocCli::Interface::Query.new
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "aoc_cli"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/aoc_cli.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'colorize'
2
+ require 'json'
3
+ require 'sqlite3'
4
+
5
+ require_relative 'aoc_cli/errors'
6
+ require_relative 'aoc_cli/interface'
7
+ require_relative "aoc_cli/version"
8
+ require_relative 'aoc_cli/files'
9
+ require_relative 'aoc_cli/commands'
10
+ require_relative 'aoc_cli/tools'
11
+ require_relative 'aoc_cli/year'
12
+ require_relative 'aoc_cli/day'
13
+ require_relative 'aoc_cli/solve'
14
+ require_relative 'aoc_cli/database'
15
+ require_relative 'aoc_cli/paths'
16
+ require_relative 'aoc_cli/tables'
17
+
18
+ module AocCli
19
+ Metafile = AocCli::Files::Metafile
20
+ Validate = AocCli::Interface::Validate
21
+ E = AocCli::Errors
22
+ end
@@ -0,0 +1,215 @@
1
+ module AocCli
2
+ module Commands
3
+ class KeyStore
4
+ attr_reader :user, :key
5
+ def initialize(args)
6
+ args = defaults.merge(args).compact
7
+ @user, @key = args[:user], args[:key]
8
+ end
9
+ def exec
10
+ Files::Cookie.new(u:user).store(key:key)
11
+ self
12
+ end
13
+ def respond
14
+ puts "Key added successfully"
15
+ end
16
+ def defaults
17
+ {user:Files::Config.new.def_acc}
18
+ end
19
+ end
20
+ class YearInit
21
+ attr_reader :user, :year
22
+ def initialize(args)
23
+ args = defaults.merge(args).compact
24
+ @user, @year = args[:user], args[:year]
25
+ end
26
+ def exec
27
+ Year::Init
28
+ .new(u:user, y:year)
29
+ .write
30
+ Year::Stars
31
+ .new(u:user, y:year)
32
+ .write.update_meta
33
+ self
34
+ end
35
+ def respond
36
+ puts "Year initialised"
37
+ end
38
+ def defaults
39
+ {user:Files::Config.new.def_acc}
40
+ end
41
+ end
42
+ class DayInit
43
+ attr_reader :user, :year, :day
44
+ def initialize(args)
45
+ args = defaults.merge(args).compact
46
+ @user = args[:user]
47
+ @year = args[:year]
48
+ @day = args[:day]
49
+ end
50
+ def exec
51
+ Day::Init
52
+ .new(u:user, y:year, d:day)
53
+ .mkdir
54
+ .write
55
+ #.init_db
56
+ Day::Pages
57
+ .new(u:user, y:year, d:day)
58
+ .write
59
+ self
60
+ end
61
+ def defaults
62
+ {user:Metafile.get(:user),
63
+ year:Metafile.get(:year)}
64
+ end
65
+ def respond
66
+ puts "Day #{day} initialised"
67
+ end
68
+ end
69
+ class DaySolve
70
+ attr_reader :user, :year, :day, :part, :ans
71
+ def initialize(args)
72
+ args = defaults.merge(args).compact
73
+ @user = args[:user]
74
+ @year = args[:year]
75
+ @day = args[:day]
76
+ @part = args[:part]
77
+ @ans = args[:ans]
78
+ end
79
+ def exec
80
+ Solve::Attempt
81
+ .new(u:user, y:year, d:day, p:part, a:ans)
82
+ .respond
83
+ self
84
+ end
85
+ def defaults
86
+ {user:Metafile.get(:user),
87
+ year:Metafile.get(:year),
88
+ day:Metafile.get(:day),
89
+ part:Metafile.get(:part)}
90
+ end
91
+ end
92
+ class OpenReddit
93
+ attr_reader :year, :day, :browser
94
+ def initialize(args)
95
+ args = defaults.merge(args).compact
96
+ @year = Validate.year(args[:year])
97
+ @day = Validate.day(args[:day])
98
+ @browser = args[:browser]
99
+ end
100
+ def exec
101
+ Day::Reddit.new(y:year, d:day, b:browser).open
102
+ self
103
+ end
104
+ def defaults
105
+ {year:Metafile.get(:year),
106
+ day:Metafile.get(:day),
107
+ browser:Files::Config
108
+ .new.get_bool(key:"browser")}
109
+ end
110
+ end
111
+ class DefaultUser
112
+ attr_reader :user, :mode
113
+ def initialize(args)
114
+ @user = args[:user]
115
+ @mode = user.nil? ? :get : :set
116
+ end
117
+ def exec
118
+ case mode
119
+ when :get then get_default
120
+ when :set then set_default end
121
+ self
122
+ end
123
+ def get_default
124
+ puts "Current default alias: "\
125
+ "#{(Files::Config.new
126
+ .get_line(key:"default") || "main")
127
+ .to_s.yellow}"
128
+ end
129
+ def set_default
130
+ Files::Config.new.mod_line(
131
+ key:"default",
132
+ val:Validate.user(user))
133
+ puts "Default account succesfully changed to "\
134
+ "#{user.yellow}"
135
+ end
136
+ end
137
+ class DefaultReddit
138
+ attr_reader :value, :mode
139
+ def initialize(args)
140
+ @value = args[:value]
141
+ @mode = value.nil? ? :get : :set
142
+ end
143
+ def exec
144
+ case mode
145
+ when :get then get_default
146
+ when :set then set_default
147
+ end
148
+ end
149
+ def get_default
150
+ puts "Always open Reddit in browser? "\
151
+ "#{Files::Config.new
152
+ .get_bool(key:"browser")
153
+ .to_s.yellow}"
154
+ end
155
+ def set_default
156
+ Files::Config.new.mod_line(
157
+ key:"browser", val:value)
158
+ puts "Reddit browser setting changed to "\
159
+ "#{value.yellow}"
160
+ end
161
+ end
162
+ class Refresh
163
+ attr_reader :dir
164
+ def initialize(args)
165
+ @dir = Metafile.type
166
+ end
167
+ def exec
168
+ case dir
169
+ when :DAY then Day.refresh
170
+ when :ROOT then Year.refresh end
171
+ self
172
+ end
173
+ end
174
+ class AttemptsTable
175
+ attr_reader :user, :year, :day, :part
176
+ def initialize(args)
177
+ args = defaults.merge(args).compact
178
+ @user = args[:user]
179
+ @year = args[:year]
180
+ @day = args[:day]
181
+ @part = args[:part]
182
+ end
183
+ def exec
184
+ Tables::Attempts
185
+ .new(u:user, y:year, d:day, p:part).show
186
+ self
187
+ end
188
+ def defaults
189
+ {user:Metafile.get(:user),
190
+ year:Metafile.get(:year),
191
+ day:Metafile.get(:day),
192
+ part:Metafile.get(:part)}
193
+ end
194
+ end
195
+ class StatsTable
196
+ attr_reader :user, :year, :day
197
+ def initialize(args)
198
+ args = defaults.merge(args).compact
199
+ @user = args[:user]
200
+ @year = args[:year]
201
+ @day = args[:day]
202
+ end
203
+ def exec
204
+ day.nil? ?
205
+ Tables::Stats::Year.new(u:user, y:year).print :
206
+ Tables::Stats::Day.new(u:user, y:year, d:day).print
207
+ end
208
+ def defaults
209
+ { user:Metafile.get(:user),
210
+ year:Metafile.get(:year),
211
+ day:Metafile.get(:day) }
212
+ end
213
+ end
214
+ end
215
+ end