aoc_cli 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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