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.
@@ -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