aoc_cli 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,93 @@
1
+ module AocCli
2
+ module Files
3
+ class Config
4
+ require 'fileutils'
5
+ def initialize
6
+ Paths::Config.create
7
+ end
8
+ def def_acc
9
+ get_line(key:"default") || "main"
10
+ end
11
+ def is_set?(key:nil, val:nil)
12
+ read.split("\n").grep(/#{key}=>#{val}/).any?
13
+ end
14
+ def mod_line(key:, val:)
15
+ is_set?(key:key) ?
16
+ write(f:read.gsub(/(?<=^#{key}=>).*$/,
17
+ val.to_s)) :
18
+ write(f:"#{key}=>#{val}\n", m:"a")
19
+ end
20
+ def get_line(key:)
21
+ read.scan(/(?<=#{key}=>).*$/)&.first
22
+ end
23
+ def get_bool(key:)
24
+ get_line(key:key) == "true" ? true : false
25
+ end
26
+ protected
27
+ def set_line(key:, val:)
28
+ write(f:"#{key}=>#{val}\n", m:"a")
29
+ end
30
+ private
31
+ def read
32
+ File.read(Paths::Config.path)
33
+ end
34
+ def write(f:, m:"w")
35
+ File.write(Paths::Config.path, f, mode:m)
36
+ end
37
+ end
38
+ class Cookie < Config
39
+ attr_reader :user
40
+ def initialize(u:)
41
+ @user = u
42
+ super()
43
+ end
44
+ def store(key:)
45
+ set_line(key:"cookie=>#{Validate.set_user(user)}",
46
+ val:Validate.set_key(key))
47
+ end
48
+ def key
49
+ get_line(key:"cookie=>#{user}")
50
+ end
51
+ end
52
+ class Metafile
53
+ def self.get(field)
54
+ read.scan(/(?<=#{field}=>).*$/)&.first&.chomp
55
+ end
56
+ def self.type
57
+ get("dir").to_sym
58
+ 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
+ def self.part(d:)
64
+ JSON.parse(read(dir:root_dir)
65
+ .scan(/(?<=stars=>).*$/)&.first)[d.to_s]
66
+ .to_i + 1
67
+ end
68
+ private
69
+ def self.read(dir:".")
70
+ File.read("#{Validate.init(dir)}/.meta")
71
+ end
72
+ def self.root_dir
73
+ type == :ROOT ? "." : ".."
74
+ end
75
+ def self.year(u:, y:)
76
+ <<~meta
77
+ dir=>ROOT
78
+ user=>#{u}
79
+ year=>#{y}
80
+ meta
81
+ end
82
+ def self.day(u:, y:, d:)
83
+ <<~meta
84
+ dir=>DAY
85
+ user=>#{u}
86
+ year=>#{y}
87
+ day=>#{d}
88
+ part=>#{part(d:d)}
89
+ meta
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,53 @@
1
+ def title(title)
2
+ "#{title.bold}"
3
+ end
4
+ def flag(short, full)
5
+ str = " #{short.yellow.bold} [#{full.blue.italic}]"
6
+ full.length > 6 ?
7
+ str += "\t" :
8
+ str += "\t\t"
9
+ str
10
+ end
11
+ puts <<~help
12
+ Advent of Code - cli, version 0.1.0
13
+ #{"C. Welham Feb 2021".italic}
14
+
15
+ #{title("Usage")}
16
+ #{" aoc".bold + " -flag".italic.yellow + " value".bold.blue}
17
+
18
+ #{title("Setup")}
19
+ - Store session cookie keys to access AoC
20
+ #{flag("-k", "--key")}Store a session cookie to use the cli.
21
+ #{flag("-u", "--user")}Set alias for key (default: "main")
22
+
23
+ #{title("Year Directory")}
24
+ - Initialise a year directory to use aoc-cli.
25
+ - If no alias is passed, the default key is used
26
+ #{flag("-y","--init-year")}Initialise directory and fetch calendar
27
+ #{flag("-u","--user")}Specify alias for initialisation
28
+ #{flag("-d","--init-day")}Create day subdirectory, fetch puzzle and input
29
+ #{flag("-r", "--refresh")}Refresh calendar
30
+
31
+ #{title("Day Subdirectory")}
32
+ - These commands can be run from the day subdirectory
33
+ #{flag("-s","--solve")}Attempt puzzle
34
+ #{flag("-r","--refresh")}Refresh puzzle
35
+ #{flag("-a","--attempts")}Prints previous attempt table
36
+ #{flag("-p","--part")}Specify part (attempts)
37
+ #{flag("-R","--reddit")}Open Reddit solution megathread
38
+ #{flag("-b","--browser")}Open Reddit thread in browser
39
+
40
+ #{title("Manual Usage")}
41
+ - AocCli uses metadata so that these flags do not need to be entered.
42
+ - Command flags can be entered manually, but this is not recommended
43
+ #{flag("-u","--user")}Specify user
44
+ #{flag("-Y","--year")}Specify year
45
+ #{flag("-D","--day")}Specify day
46
+ #{flag("-p","--part")}Specify part
47
+
48
+ #{title("Configuration")}
49
+ - Pass with a value to update setting
50
+ - Pass without an argument to display current setting.
51
+ #{flag("-B","--browser")}Always open Reddit in browser (default: false)
52
+ #{flag("-U","--default")}Default key alias to use (default: "main")
53
+ help
@@ -0,0 +1,137 @@
1
+ module AocCli
2
+ module Interface
3
+ class Query
4
+ def initialize
5
+ ARGV.size > 0 ?
6
+ run(opts:Opts.new.parse_args) :
7
+ puts(Help.print)
8
+ rescue StandardError => e
9
+ abort e.message
10
+ end
11
+ def run(opts:)
12
+ cmd = Object
13
+ .const_get("AocCli::Commands::#{opts.cmd}")
14
+ .new(opts.args)
15
+ .exec
16
+ cmd.respond if cmd.class.instance_methods.include?(:respond)
17
+ end
18
+ end
19
+ class Help
20
+ def self.print
21
+ require_relative "help.rb"
22
+ end
23
+ end
24
+ class Opts
25
+ attr_reader :cmd, :args
26
+ def initialize
27
+ @args = {}
28
+ end
29
+ def parse_args
30
+ while ARGV.size > 0
31
+ case ARGV.shift
32
+ when "-a", "--attempts"
33
+ @cmd = :AttemptsTable
34
+ when "-b", "--browser"
35
+ args[:browser] = true
36
+ when "-d", "--init-day"
37
+ @cmd = :DayInit
38
+ args[:day] = Validate.day(ARGV.shift.to_i)
39
+ when "-h", "--help"
40
+ exit Help.print
41
+ when "-k", "--key"
42
+ @cmd = :KeyStore
43
+ args[:key] = Validate.set_key(ARGV.shift)
44
+ when "-p", "--part"
45
+ args[:part] = Validate.part(ARGV.shift.to_i)
46
+ when "-r", "--refresh"
47
+ @cmd = :Refresh
48
+ when "-s", "--solve"
49
+ @cmd = :DaySolve
50
+ args[:ans] = Validate.ans(ARGV.shift)
51
+ when "-u", "--user"
52
+ args[:user] = ARGV.shift
53
+ when "-y", "--init-year"
54
+ @cmd = :YearInit
55
+ args[:year] = Validate.year(ARGV.shift.to_i)
56
+ when "-B", "--browser"
57
+ @cmd = :DefaultReddit
58
+ args[:value] = ARGV.shift
59
+ when "-D", "--day"
60
+ args[:day] = Validate.day(ARGV.shift.to_i)
61
+ when "-R", "--reddit"
62
+ @cmd = :OpenReddit
63
+ when "-S", "--stats"
64
+ @cmd = :StatsTable
65
+ when "-U", "--default-user"
66
+ @cmd = :DefaultUser
67
+ args[:user] = ARGV.shift
68
+ when "-Y", "--year"
69
+ args[:year] = Validate.year(ARGV.shift.to_i)
70
+ else raise E::FlagInv
71
+ end
72
+ end
73
+ raise E::NoCmd if cmd.nil?
74
+ self
75
+ end
76
+ end
77
+ class Validate
78
+ def self.user(user)
79
+ raise E::UserNil if user.nil?
80
+ raise E::UserInv.new(user) unless Files::Config
81
+ .new.is_set?(key:"cookie=>#{user}")
82
+ user
83
+ end
84
+ def self.set_user(user)
85
+ raise E::UserNil if user.nil?
86
+ raise E::UserDup if Files::Config.new
87
+ .is_set?(key:"cookie=>#{user}")
88
+ user
89
+ end
90
+ def self.year(year)
91
+ raise E::YearNil if year.nil?
92
+ raise E::YearInv.new(year) if year.to_i < 2015 ||
93
+ year.to_i > 2020
94
+ year
95
+ end
96
+ def self.day(day)
97
+ raise E::DayNil if day.nil? || day == 0
98
+ raise E::DayInv.new(day) if day.to_i < 1 ||
99
+ day.to_i > 25
100
+ day
101
+ end
102
+ def self.part(part)
103
+ raise E::PartNil if part.nil?
104
+ raise E::PuzzComp if part.to_i == 3
105
+ raise E::PartInv if part.to_i < 1 || part.to_i > 2
106
+ part
107
+ end
108
+ def self.set_key(key)
109
+ raise E::KeyNil if key.nil?
110
+ raise E::KeyDup if Files::Config
111
+ .new.is_set?(val:key)
112
+ key
113
+ end
114
+ def self.get_key(key)
115
+ raise E::KeyNil if key.nil?
116
+ key
117
+ end
118
+ def self.ans(ans)
119
+ raise E::AnsNil if ans.nil?
120
+ ans
121
+ end
122
+ def self.day_dir(day)
123
+ raise E::DayExist.new(day) if Dir.exist?(day)
124
+ day
125
+ end
126
+ def self.init(dir)
127
+ raise E::NotInit unless File.exist?("#{dir}/.meta")
128
+ dir
129
+ end
130
+ def self.not_init(dir:, year:)
131
+ raise E::AlrInit if File.exist?("#{dir}/.meta") &&
132
+ Metafile.get(:year) != year.to_s
133
+ dir
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,93 @@
1
+ module AocCli
2
+ module Paths
3
+ class Config
4
+ def self.create
5
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
6
+ File.write(path, "", mode:"a") unless File
7
+ .exist?(path)
8
+ end
9
+ def self.dir
10
+ "#{Dir.home}/.config/aoc-cli"
11
+ end
12
+ def self.path
13
+ "#{dir}/aoc.rc"
14
+ end
15
+ end
16
+ class Day
17
+ attr_reader :user, :year, :day
18
+ def initialize(u:Metafile.get(:user),
19
+ y:Metafile.get(:year), d:)
20
+ @user = Validate.user(u)
21
+ @year = Validate.year(y)
22
+ @day = Validate.day(d)
23
+ end
24
+ def filename(f:)
25
+ case f.to_sym
26
+ when :Input then "input"
27
+ when :Puzzle then "#{day}.md"
28
+ when :meta then ".meta"
29
+ end
30
+ end
31
+ def in_day?
32
+ Metafile.type == :DAY
33
+ end
34
+ def day_dir
35
+ day.to_i < 10 ? "0#{day}" : day.to_s
36
+ end
37
+ def local_dir
38
+ in_day? ? "." : "#{day_dir}"
39
+ end
40
+ def cache_dir
41
+ "#{Dir.home}/.cache/aoc-cli/"\
42
+ "#{user}/#{year}/#{day_dir}"
43
+ end
44
+ def local(f:)
45
+ "#{local_dir}/#{filename(f:f)}"
46
+ end
47
+ def cache_path(f:)
48
+ "#{cache_dir}/#{filename(f:f)}"
49
+ end
50
+ def cache_and_local(f:)
51
+ [cache_path(f:f), local(f:f)]
52
+ end
53
+ end
54
+ class Year
55
+ attr_reader :user, :year
56
+ def initialize(u:Metafile.get(:user),
57
+ y:Metafile.get(:year))
58
+ @user = Validate.user(u)
59
+ @year = Validate.year(y)
60
+ end
61
+ def in_year?
62
+ File.exist?("./.meta") ?
63
+ Metafile.type == :ROOT : true
64
+ end
65
+ def year_dir
66
+ in_year? ? "." : ".."
67
+ end
68
+ def local(f:)
69
+ "#{Validate.not_init(dir:year_dir,
70
+ year:year)}/#{filename(f:f)}"
71
+ end
72
+ def filename(f:)
73
+ case f.to_sym
74
+ when :Stars then "#{year}.md"
75
+ when :meta then ".meta"
76
+ end
77
+ end
78
+ end
79
+ class Database < Config
80
+ def self.dir
81
+ "#{super}/db"
82
+ end
83
+ def self.cfg(name)
84
+ FileUtils.mkdir_p(dir) unless Dir
85
+ .exist?(dir)
86
+ "#{dir}/#{name}.db"
87
+ end
88
+ def self.root(name)
89
+ "#{__dir__}/db/#{name}.db"
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,109 @@
1
+ module AocCli
2
+ module Solve
3
+ CORRECT = "That's the right answer"
4
+ INCORRECT = "That's not the right answer"
5
+ WAIT = "You gave an answer too recently"
6
+ class Attempt
7
+ attr_reader :user, :year, :day, :part, :answer
8
+ def initialize(u:Metafile.get(:user),
9
+ y:Metafile.get(:year),
10
+ d:Metafile.get(:day),
11
+ p:Metafile.get(:part), a:)
12
+ @user = Validate.user(u)
13
+ @year = Validate.year(y)
14
+ @day = Validate.day(d)
15
+ @part = Validate.part(p)
16
+ @answer = Validate.ans(a)
17
+ end
18
+ def raw
19
+ @raw ||= Tools::Post
20
+ .new(u:user, y:year, d:day,
21
+ data:{level:part, answer:answer})
22
+ .plain
23
+ end
24
+ def check
25
+ case raw
26
+ when /#{CORRECT}/ then :Correct
27
+ when /#{INCORRECT}/ then :Incorrect
28
+ when /#{WAIT}/ then :Wait
29
+ end
30
+ end
31
+ def respond
32
+ Object
33
+ .const_get("AocCli::Solve::Respond::#{check}")
34
+ .new(attempt:self)
35
+ .respond
36
+ .react
37
+ end
38
+ end
39
+ module Respond
40
+ class Response
41
+ attr_reader :attempt
42
+ def initialize(attempt:)
43
+ @attempt = attempt
44
+ end
45
+ end
46
+ class Correct < Response
47
+ def react
48
+ log = Database::Log
49
+ .new(attempt:attempt)
50
+ .correct
51
+ Database::Stats::Complete
52
+ .new(n:log.count_attempts)
53
+ .update
54
+ Year.refresh
55
+ Day.refresh
56
+ Database::Stats::Init.new.init
57
+ end
58
+ def respond
59
+ response = "#{"Correct!".bold.green} "
60
+ response += case attempt.part
61
+ when "1" then next_part
62
+ when "2" then complete end
63
+ puts response
64
+ self
65
+ end
66
+ private
67
+ def next_part
68
+ "Downloading part two.."
69
+ end
70
+ def complete
71
+ "This day is now complete!".green
72
+ end
73
+ end
74
+ class Incorrect < Response
75
+ def react
76
+ Database::Log
77
+ .new(attempt:attempt)
78
+ .incorrect(high:high, low:low)
79
+ end
80
+ def respond
81
+ response = "#{"Incorrect".red.bold}: "\
82
+ "You guessed - #{attempt.answer.to_s.red}\n"
83
+ response += "This answer is too high\n" if high
84
+ response += "This answer is too low\n" if low
85
+ puts response
86
+ self
87
+ end
88
+ def high
89
+ /too high/.match?(attempt.raw)
90
+ end
91
+ def low
92
+ /too low/.match?(attempt.raw)
93
+ end
94
+ end
95
+ class Wait < Response
96
+ def respond
97
+ response = "#{"Please wait".yellow.bold}: "\
98
+ "You have #{time.to_s} to wait"
99
+ puts response
100
+ self
101
+ end
102
+ def time
103
+ attempt.raw.scan(/(?:\d+m\s)?\d+s/).first.to_s
104
+ end
105
+ def react; end
106
+ end
107
+ end
108
+ end
109
+ end