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.
data/lib/aoc_cli/help.rb CHANGED
@@ -8,8 +8,9 @@ def flag(short, full)
8
8
  str += "\t\t"
9
9
  str
10
10
  end
11
- puts <<~help
12
- Advent of Code - cli, version 0.1.0
11
+
12
+ help = <<~help
13
+ Advent of Code - cli, version #{AocCli::VERSION}
13
14
  #{"C. Welham Feb 2021".italic}
14
15
 
15
16
  #{title("Usage")}
@@ -17,12 +18,16 @@ Advent of Code - cli, version 0.1.0
17
18
 
18
19
  #{title("Setup")}
19
20
  - 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")
21
+
22
+ #{flag("-k","--key")}Store a session cookie to use the cli.
23
+ #{flag("-u","--user")}Set alias for key (default: "main")
24
+ #{flag("-U","--default")}Get/set default alias
25
+ #{flag("-h","--help")}Print this screen
22
26
 
23
27
  #{title("Year Directory")}
24
28
  - Initialise a year directory to use aoc-cli.
25
29
  - If no alias is passed, the default key is used
30
+
26
31
  #{flag("-y","--init-year")}Initialise directory and fetch calendar
27
32
  #{flag("-u","--user")}Specify alias for initialisation
28
33
  #{flag("-d","--init-day")}Create day subdirectory, fetch puzzle and input
@@ -30,16 +35,21 @@ Advent of Code - cli, version 0.1.0
30
35
 
31
36
  #{title("Day Subdirectory")}
32
37
  - These commands can be run from the day subdirectory
38
+
33
39
  #{flag("-s","--solve")}Attempt puzzle
34
- #{flag("-r","--refresh")}Refresh puzzle
35
- #{flag("-a","--attempts")}Prints previous attempt table
36
40
  #{flag("-p","--part")}Specify part (attempts)
41
+ #{flag("-r","--refresh")}Refresh puzzle
42
+
43
+ #{title("Reddit")}
44
+ - Defaults to a Reddit CLI if one is installed
45
+
37
46
  #{flag("-R","--reddit")}Open Reddit solution megathread
38
- #{flag("-b","--browser")}Open Reddit thread in browser
47
+ #{flag("-B","--browser")}Open Reddit thread in browser
39
48
 
40
49
  #{title("Manual Usage")}
41
50
  - AocCli uses metadata so that these flags do not need to be entered.
42
51
  - Command flags can be entered manually, but this is not recommended
52
+
43
53
  #{flag("-u","--user")}Specify user
44
54
  #{flag("-Y","--year")}Specify year
45
55
  #{flag("-D","--day")}Specify day
@@ -48,6 +58,17 @@ Advent of Code - cli, version 0.1.0
48
58
  #{title("Configuration")}
49
59
  - Pass with a value to update setting
50
60
  - 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")
61
+
62
+ #{flag("-U","--default")}Default key alias to use
63
+ #{flag("-G","--gen-config")}Creates an example config file
64
+
65
+ #{title("Tables")}
66
+ - Print stats in a terminal-friendly table
67
+
68
+ #{flag("-a","--attempts")}Prints previous attempts for puzzle (part needed)
69
+ #{flag("-c","--simple-cal")}Prints your progress
70
+ #{flag("-C","--fancy-cal")}Prints your calendar file
71
+ #{flag("-S","--stats")}Prints your stats (time taken, number of attempts)
72
+
53
73
  help
74
+ system("echo '#{help}' | less -r")
@@ -3,17 +3,16 @@ module AocCli
3
3
  class Query
4
4
  def initialize
5
5
  ARGV.size > 0 ?
6
- run(opts:Opts.new.parse_args) :
7
- puts(Help.print)
6
+ run(opts:Opts.new.parse_args) : Help.print
8
7
  rescue StandardError => e
9
8
  abort e.message
10
9
  end
11
10
  def run(opts:)
12
- cmd = Object
13
- .const_get("AocCli::Commands::#{opts.cmd}")
11
+ cmd = Commands.const_get(Validate.cmd(opts.cmd))
14
12
  .new(opts.args)
15
13
  .exec
16
- cmd.respond if cmd.class.instance_methods.include?(:respond)
14
+ cmd.respond if cmd.class
15
+ .instance_methods.include?(:respond)
17
16
  end
18
17
  end
19
18
  class Help
@@ -31,60 +30,65 @@ module AocCli
31
30
  case ARGV.shift
32
31
  when "-a", "--attempts"
33
32
  @cmd = :AttemptsTable
34
- when "-b", "--browser"
33
+ when "-B", "--browser"
34
+ @cmd = :OpenReddit
35
35
  args[:browser] = true
36
+ when "-c", "--simple-cal"
37
+ @cmd = :CalendarTable
38
+ when "-C", "--fancy-cal"
39
+ @cmd = :PrintCal
36
40
  when "-d", "--init-day"
37
41
  @cmd = :DayInit
38
42
  args[:day] = Validate.day(ARGV.shift.to_i)
43
+ when "-D", "--day"
44
+ args[:day] = ARGV.shift.to_i
45
+ when "-G", "--gen-config"
46
+ @cmd = :GenerateConfig
39
47
  when "-h", "--help"
40
48
  exit Help.print
41
49
  when "-k", "--key"
42
50
  @cmd = :KeyStore
43
51
  args[:key] = Validate.set_key(ARGV.shift)
44
52
  when "-p", "--part"
45
- args[:part] = Validate.part(ARGV.shift.to_i)
53
+ args[:part] = ARGV.shift.to_i
46
54
  when "-r", "--refresh"
47
55
  @cmd = :Refresh
56
+ when "-R", "--reddit"
57
+ @cmd = :OpenReddit
48
58
  when "-s", "--solve"
49
59
  @cmd = :DaySolve
50
60
  args[:ans] = Validate.ans(ARGV.shift)
61
+ when "-S", "--stats"
62
+ @cmd = :StatsTable
51
63
  when "-u", "--user"
52
64
  args[:user] = ARGV.shift
65
+ when "-U", "--default"
66
+ @cmd = :DefaultAlias
67
+ args[:user] = ARGV.shift
53
68
  when "-y", "--init-year"
54
69
  @cmd = :YearInit
55
70
  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
71
  when "-Y", "--year"
69
- args[:year] = Validate.year(ARGV.shift.to_i)
72
+ args[:year] = ARGV.shift.to_i
70
73
  else raise E::FlagInv
71
74
  end
72
75
  end
73
- raise E::NoCmd if cmd.nil?
74
76
  self
75
77
  end
76
78
  end
77
79
  class Validate
80
+ def self.cmd(cmd)
81
+ raise E::CmdNil if cmd.nil?
82
+ cmd
83
+ end
78
84
  def self.user(user)
79
85
  raise E::UserNil if user.nil?
80
- raise E::UserInv.new(user) unless Files::Config
81
- .new.is_set?(key:"cookie=>#{user}")
86
+ raise E::UserInv.new(user) unless user_in_config?(user)
82
87
  user
83
88
  end
84
89
  def self.set_user(user)
85
90
  raise E::UserNil if user.nil?
86
- raise E::UserDup if Files::Config.new
87
- .is_set?(key:"cookie=>#{user}")
91
+ raise E::UserDup.new(user) if user_in_config?(user)
88
92
  user
89
93
  end
90
94
  def self.year(year)
@@ -107,12 +111,14 @@ module AocCli
107
111
  end
108
112
  def self.set_key(key)
109
113
  raise E::KeyNil if key.nil?
110
- raise E::KeyDup if Files::Config
111
- .new.is_set?(val:key)
114
+ raise E::KeyDup.new(key) if Files::Config::Tools
115
+ .is_set?(val:"#{key}(\b|$)")
116
+ raise E::KeyInv unless valid_key?(key)
112
117
  key
113
118
  end
114
- def self.get_key(key)
119
+ def self.key(key)
115
120
  raise E::KeyNil if key.nil?
121
+ raise E::KeyInv unless valid_key?(key)
116
122
  key
117
123
  end
118
124
  def self.ans(ans)
@@ -132,6 +138,17 @@ module AocCli
132
138
  Metafile.get(:year) != year.to_s
133
139
  dir
134
140
  end
141
+ def self.no_config
142
+ raise E::ConfigExist if File.exist?(Paths::Config.path)
143
+ Paths::Config.path
144
+ end
145
+ private
146
+ def self.valid_key?(key)
147
+ /session=(?:[a-f0-9]){96}/.match?(key)
148
+ end
149
+ def self.user_in_config?(user)
150
+ Files::Config::Tools.is_set?(key:"cookie=>#{user}")
151
+ end
135
152
  end
136
153
  end
137
154
  end
data/lib/aoc_cli/paths.rb CHANGED
@@ -1,16 +1,29 @@
1
1
  module AocCli
2
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)
3
+ require 'fileutils'
4
+ class Year
5
+ attr_reader :user, :year
6
+ def initialize(u:Metafile.get(:user),
7
+ y:Metafile.get(:year))
8
+ @user = Validate.user(u)
9
+ @year = Validate.year(y)
8
10
  end
9
- def self.dir
10
- "#{Dir.home}/.config/aoc-cli"
11
+ def in_year?
12
+ File.exist?("./.meta") ?
13
+ Metafile.type == :ROOT : true
11
14
  end
12
- def self.path
13
- "#{dir}/aoc.rc"
15
+ def year_dir
16
+ in_year? ? "." : ".."
17
+ end
18
+ def local(f:)
19
+ "#{Validate.not_init(dir:year_dir,
20
+ year:year)}/#{filename(f:f)}"
21
+ end
22
+ def filename(f:)
23
+ case f.to_sym
24
+ when :Stars then "#{year}.md"
25
+ when :meta then ".meta"
26
+ end
14
27
  end
15
28
  end
16
29
  class Day
@@ -21,6 +34,9 @@ module AocCli
21
34
  @year = Validate.year(y)
22
35
  @day = Validate.day(d)
23
36
  end
37
+ def create_cache
38
+ FileUtils.mkdir_p(cache_dir) unless Dir.exist?(cache_dir)
39
+ end
24
40
  def filename(f:)
25
41
  case f.to_sym
26
42
  when :Input then "input"
@@ -51,29 +67,16 @@ module AocCli
51
67
  [cache_path(f:f), local(f:f)]
52
68
  end
53
69
  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? ? "." : ".."
70
+ class Config
71
+ def self.create
72
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
73
+ File.write(path, "", mode:"a") unless File.exist?(path)
67
74
  end
68
- def local(f:)
69
- "#{Validate.not_init(dir:year_dir,
70
- year:year)}/#{filename(f:f)}"
75
+ def self.dir
76
+ "#{Dir.home}/.config/aoc-cli"
71
77
  end
72
- def filename(f:)
73
- case f.to_sym
74
- when :Stars then "#{year}.md"
75
- when :meta then ".meta"
76
- end
78
+ def self.path
79
+ "#{dir}/aoc.rc"
77
80
  end
78
81
  end
79
82
  class Database < Config
data/lib/aoc_cli/solve.rb CHANGED
@@ -16,10 +16,8 @@ module AocCli
16
16
  @answer = Validate.ans(a)
17
17
  end
18
18
  def raw
19
- @raw ||= Tools::Post
20
- .new(u:user, y:year, d:day,
21
- data:{level:part, answer:answer})
22
- .plain
19
+ @raw ||= Tools::Post.new(u:user, y:year, d:day,
20
+ data:{level:part, answer:answer}).plain
23
21
  end
24
22
  def check
25
23
  case raw
@@ -29,8 +27,7 @@ module AocCli
29
27
  end
30
28
  end
31
29
  def respond
32
- Object
33
- .const_get("AocCli::Solve::Respond::#{check}")
30
+ Respond.const_get(check)
34
31
  .new(attempt:self)
35
32
  .respond
36
33
  .react
@@ -45,44 +42,35 @@ module AocCli
45
42
  end
46
43
  class Correct < Response
47
44
  def react
48
- log = Database::Log
49
- .new(attempt:attempt)
50
- .correct
51
- Database::Stats::Complete
52
- .new(n:log.count_attempts)
53
- .update
45
+ Database.correct(attempt:attempt)
54
46
  Year.refresh
55
- Day.refresh
56
- Database::Stats::Init.new.init
47
+ Day.refresh(files:[:Puzzle])
57
48
  end
58
49
  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
50
+ puts <<~response
51
+ #{"Correct!".bold.green} #{
52
+ case attempt.part
53
+ when "1" then "Downloading part two..."
54
+ when "2" then "This day is now complete!".green
55
+ end }
56
+ response
64
57
  self
65
58
  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
59
  end
74
60
  class Incorrect < Response
75
61
  def react
76
- Database::Log
62
+ Database::Attempt
77
63
  .new(attempt:attempt)
78
64
  .incorrect(high:high, low:low)
79
65
  end
80
66
  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
67
+ puts <<~response
68
+ #{"Incorrect".red.bold}: You guessed - #{attempt
69
+ .answer.to_s.red} #{
70
+ high ? "(too high)" :
71
+ low ? "(too low)" : ""}
72
+ #{"Please wait".yellow} #{wait_time} before answering again
73
+ response
86
74
  self
87
75
  end
88
76
  def high
@@ -91,12 +79,16 @@ module AocCli
91
79
  def low
92
80
  /too low/.match?(attempt.raw)
93
81
  end
82
+ def wait_time
83
+ attempt.raw.scan(/(?:(one minute|\d+ minutes))/)
84
+ .first.first.to_s
85
+ end
94
86
  end
95
87
  class Wait < Response
96
88
  def respond
97
- response = "#{"Please wait".yellow.bold}: "\
98
- "You have #{time.to_s} to wait"
99
- puts response
89
+ puts <<~response
90
+ #{"Please wait".yellow.bold}: #{time.to_s}
91
+ response
100
92
  self
101
93
  end
102
94
  def time
@@ -1,34 +1,47 @@
1
1
  module AocCli
2
2
  module Tables
3
- class Attempts
3
+ class Table
4
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))
5
+ attr_reader :user, :year, :table, :cols, :where
6
+ def initialize(u:Metafile.get(:user), y:Metafile.get(:year))
10
7
  @user = Validate.user(u)
11
8
  @year = Validate.year(y)
12
- @day = Validate.day(d)
13
- @part = Validate.part(p)
14
- @db = Database::Query
9
+ end
10
+ def border
11
+ Prefs.bool(key:"unicode_tables") ? :unicode : :ascii
12
+ end
13
+ def data
14
+ Database::Query
15
15
  .new(path:Paths::Database.cfg(user))
16
+ .select(t:table, cols:cols, where:where)
16
17
  end
17
- def show
18
- puts rows.count > 0 ? table :
19
- "You have not attempted this puzzle yet!"
18
+ def print
19
+ puts rows.count > 0 ? make : nil_message
20
20
  end
21
- private
22
- def table
21
+ def make
23
22
  tab = Terminal::Table.new(
24
23
  :headings => headings,
25
24
  :rows => rows,
26
25
  :title => title)
27
26
  tab.style = {
28
- :border => :unicode,
27
+ :border => border,
29
28
  :alignment => :center}
30
29
  tab
31
30
  end
31
+ end
32
+ class Attempts < Table
33
+ attr_reader :day, :part
34
+ def initialize(u:Metafile.get(:user),
35
+ y:Metafile.get(:year),
36
+ d:Metafile.get(:day),
37
+ p:Metafile.get(:part))
38
+ super(u:u, y:y)
39
+ @day = Validate.day(d)
40
+ @part = Validate.part(p)
41
+ @table = :attempts
42
+ @cols = "time, answer, high, low, correct"
43
+ @where = {year:year, day:day, part:part}
44
+ end
32
45
  def title
33
46
  "#{year} - Day #{day}:#{part}".bold
34
47
  end
@@ -36,56 +49,33 @@ module AocCli
36
49
  ["Answer", "Time", "Hint"]
37
50
  end
38
51
  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")
52
+ @rows ||= data.map do |d|
53
+ [parse_ans(d), parse_time(d), parse_hint(d)]
54
+ end
55
+ end
56
+ def parse_ans(row)
57
+ row[4] == 1 ? row[1].to_s.green : row[1].to_s.red
58
+ end
59
+ def parse_time(row)
60
+ DateTime.strptime(row[0], "%Y-%m-%d %H:%M:%S %Z")
56
61
  .strftime("%H:%M - %d/%m/%y")
57
62
  end
58
- def parse_hint(attempt)
59
- attempt[2] == 1 ? "low" :
60
- attempt[3] == 1 ? "high" : "-"
63
+ def parse_hint(row)
64
+ row[3] == 1 ? "low" :
65
+ row[2] == 1 ? "high" : "-"
66
+ end
67
+ def nil_message
68
+ "You have not attempted part #{part} yet!"
61
69
  end
62
70
  end
63
71
  module Stats
64
- class Year
65
- attr_reader :user, :year, :db
72
+ class Year < Table
66
73
  def initialize(u:Metafile.get(:user),
67
74
  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
75
+ super(u:u, y:y)
76
+ @table = :stats
77
+ @cols = "day, part, attempts, elapsed"
78
+ @where = {year:"'#{year}'", correct:"'1'"}
89
79
  end
90
80
  def title
91
81
  "Year #{year}"
@@ -93,15 +83,11 @@ module AocCli
93
83
  def headings
94
84
  ["Day", "Part", "Attempts", "Time (h:m:s)"]
95
85
  end
96
- def stats
97
- @stats ||= db.select(
98
- t:"stats",
99
- cols:"day, part, attempts, elapsed",
100
- data:where)
86
+ def rows
87
+ @rows ||= data.map{|d| [d[0], d[1], d[2], d[3]]}
101
88
  end
102
- def where
103
- {year:"'#{year}'",
104
- correct:"'1'"}
89
+ def nil_message
90
+ "You have not completed any puzzles yet"
105
91
  end
106
92
  end
107
93
  class Day < Year
@@ -111,9 +97,10 @@ module AocCli
111
97
  d:Metafile.get(:day))
112
98
  super(u:u, y:y)
113
99
  @day = Validate.day(d)
114
- end
115
- def rows
116
- @rows ||= stats.map{|s| [s[0], s[1], s[2]]}
100
+ @cols = "part, attempts, elapsed"
101
+ @where = { year:"'#{year}'",
102
+ day:"'#{day}'",
103
+ correct:"'1'" }
117
104
  end
118
105
  def title
119
106
  "Year #{year}: Day #{day}"
@@ -121,18 +108,31 @@ module AocCli
121
108
  def headings
122
109
  ["Part", "Attempts", "Time (h:m:s)"]
123
110
  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'"}
111
+ def rows
112
+ @rows ||= data.map{|d| [d[0], d[1], d[2]]}
134
113
  end
135
114
  end
136
115
  end
116
+ class Calendar < Table
117
+ def initialize(u:Metafile.get(:user),
118
+ y:Metafile.get(:year))
119
+ super(u:u, y:y)
120
+ @table = :calendar
121
+ @cols = "*"
122
+ @where = {year:year}
123
+ end
124
+ def title
125
+ "#{user}: #{year}"
126
+ end
127
+ def headings
128
+ ["Day", "Stars"]
129
+ end
130
+ def rows
131
+ @rows ||= data.map{|d| [d[1], parse_stars(d[2])]}
132
+ end
133
+ def parse_stars(day)
134
+ day.to_i == 0 ? ".." : ("*" * day.to_i).ljust(2, ".")
135
+ end
136
+ end
137
137
  end
138
138
  end