Skip to content

Commit

Permalink
feat: minor changes to command line options
Browse files Browse the repository at this point in the history
- read now only shows puzzle statement on the terminal (it no longer
  saves it to file)
- download now saves both input and description to files
- choose what to download with  --input-only and --description-only
  • Loading branch information
scarvalhojr committed Dec 2, 2022
1 parent 7bef073 commit 57b9e37
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 115 deletions.
8 changes: 5 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ In particular, the commit message should start with one of the following types:

- Detect when session cookie is expired (currently user gets a HTTP 500 error).

- Warn user if session file permissions allow reading by others.

- Add option to wait until the next puzzle unlocks with a countdown timer (for
read and download commands).

- Commands:
- `[b]egin` - read puzzle description and download puzzle input (make it the
default command) with an option to wait until the next puzzle unlocks with
a countdown timer.
- `[c]alendar` - show progress calendar (stars collected each day).
- `[d]ownload` - already implemented!
- `[g]lobal-leaderboard` - show [global leaderboard](https://adventofcode.com/2022/leaderboard).
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "aoc-cli"
description = "Advent of Code command-line helper tool"
version = "0.5.1"
description = "Advent of Code command-line tool"
version = "0.6.0"
authors = ["Sergio de Carvalho <[email protected]>"]
categories = ["command-line-utilities"]
edition = "2021"
Expand Down
63 changes: 35 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ directory. This file should only contain your session number, in a single line.
```
# aoc help
Advent of Code command-line helper tool
Advent of Code command-line tool
Usage: aoc [OPTIONS] [COMMAND]
Commands:
read Read puzzle statement (the default command) [aliases: r]
download Download puzzle input [aliases: d]
download Save puzzle description and input to files [aliases: d]
submit Submit puzzle answer [aliases: s]
help Print this message or the help of the given subcommand(s)
Expand All @@ -111,7 +111,9 @@ Options:
-y, --year <YEAR> Puzzle year [default: year of current or last Advent of Code event]
-s, --session-file <PATH> Path to session cookie file [default: ~/.adventofcode.session]
-w, --width <WIDTH> Width at which to wrap output [default: terminal width]
-o, --overwrite Overwrite file if it already exists
-o, --overwrite Overwrite files if they already exist
-I, --input-only Download puzzle input only
-D, --description-only Download puzzle description only
-i, --input-file <PATH> Path where to save puzzle input [default: input]
-p, --puzzle-file <PATH> Path where to save puzzle description [default: puzzle.md]
-h, --help Print help information
Expand All @@ -123,46 +125,56 @@ Options:
Submit puzzle answer
Usage: aoc submit <PART> <ANSWER>
Usage: aoc submit [OPTIONS] <PART> <ANSWER>
Arguments:
<PART> Puzzle part [possible values: 1, 2]
<ANSWER> Puzzle answer
Options:
-h, --help Print help information
-d, --day <DAY> Puzzle day [default: last unlocked day (during Advent of Code month)]
-y, --year <YEAR> Puzzle year [default: year of current or last Advent of Code event]
-s, --session-file <PATH> Path to session cookie file [default: ~/.adventofcode.session]
-w, --width <WIDTH> Width at which to wrap output [default: terminal width]
-o, --overwrite Overwrite files if they already exist
-I, --input-only Download puzzle input only
-D, --description-only Download puzzle description only
-i, --input-file <PATH> Path where to save puzzle input [default: input]
-p, --puzzle-file <PATH> Path where to save puzzle description [default: puzzle.md]
-h, --help Print help information
```

### Read puzzle description

Read today's puzzle (if today is an Advent of Code day) in plain text from the
comfort of your terminal. This command also saves the puzzle description to a
file in Markdown format.
comfort of your terminal.

```
# aoc read
## --- Day 5: Binary Boarding ---
Loaded session cookie from "/home/user/.adventofcode.session".
You board your plane only to discover a new problem: you dropped your boarding
pass! You aren't sure which seat is yours, and all of the flight attendants are
busy with the flood of people that suddenly made it through passport control.
...
## --- Day 2: Rock Paper Scissors ---
Saving puzzle description to "puzzle.md"...
Done!
The Elves begin to set up camp on the beach. To decide whose tent gets to be
closest to the snack storage, a giant [Rock Paper Scissors][1] tournament is
already in progress.
...
```

### Download puzzle input

Download input for today's puzzle and save it to a file named "input" on the
current directory:
Download description and input for today's puzzle and save them to files. By
default the description is saved to "puzzle.md" and the input is saved to
"input" in the current directory:

```
# aoc download
Loaded session cookie from "/home/user/.adventofcode.session".
Downloading input for day 5, 2020...
Fetching puzzle for day 2, 2022...
Saving puzzle description to "puzzle.md"...
Downloading input for day 2, 2022...
Saving puzzle input to "input"...
Done!
```
Expand All @@ -176,11 +188,11 @@ Submit the answer to part 1 of today's puzzle (in this example, the answer is
# aoc submit 1 999
Loaded session cookie from "/home/user/.adventofcode.session".
Submitting answer for part 1, day 5, 2020...
Submitting answer for part 1, day 2, 2022...
That's the right answer! You are one gold star closer to saving your vacation. [[Continue to Part Two]][1]
[1] /2020/day/5#part2
[1] /2022/day/2#part2
```

### Command abbreviations
Expand All @@ -192,25 +204,20 @@ Any prefix of a command can be used instead of the full command name. For instan

### More examples

Download the input from a previous day (assumes the current year):
Download puzzle from a previous day (assumes the current year):

```
# aoc download --day 1
Loaded session cookie from "/home/user/.adventofcode.session".
Downloading input for day 1, 2020...
Saving puzzle input to "input"...
Done!
```

Download the input from a previous year and save it to a file with a given
Download puzzle input from a previous year and save it to a file with a given
name:

```
# aoc download --year 2015 --day 1 --input-file /home/user/aoc/2015/1/input
# aoc download --year 2015 --day 1 --input-only --input-file /home/user/aoc/2015/1/input
```

An attempt to download the input of a puzzle that is still locked fails
An attempt to download a puzzle that is still locked fails
(puzzles unlock every day between 1st and 25th of December at midnight
EST/UTC-5):

Expand Down
129 changes: 68 additions & 61 deletions src/aoc.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::args::Args;
use chrono::{Datelike, FixedOffset, NaiveDate, TimeZone, Utc};
use html2md::parse_html;
use html2text::from_read;
Expand Down Expand Up @@ -108,27 +109,54 @@ fn build_client(
.map_err(|err| err.to_string())
}

pub fn download_input(
fn get_description(
session_cookie: &str,
opt_year: Option<PuzzleYear>,
opt_day: Option<PuzzleDay>,
filename: &str,
overwrite: bool,
) -> Result<(), String> {
let (year, day) = puzzle_year_day(opt_year, opt_day)?;
year: PuzzleYear,
day: PuzzleDay,
) -> Result<String, String> {
eprintln!("Fetching puzzle for day {}, {}...", day, year);

eprintln!("Downloading input for day {}, {}...", day, year);
let url = format!("https://adventofcode.com/{}/day/{}/input", year, day);
let content_type = "text/plain";
let puzzle_input = build_client(session_cookie, content_type)?
let url = format!("https://adventofcode.com/{}/day/{}", year, day);
let response = build_client(session_cookie, "text/html")?
.get(&url)
.send()
.and_then(|response| response.error_for_status())
.and_then(|response| response.text())
.map_err(|err| err.to_string())?;

eprintln!("Saving puzzle input to \"{}\"...", filename);
let desc = Regex::new(r"(?i)(?s)<main>(?P<main>.*)</main>")
.unwrap()
.captures(&response)
.ok_or("Failed to parse puzzle description")?
.name("main")
.unwrap()
.as_str()
.to_string();

Ok(desc)
}

fn get_input(
session_cookie: &str,
year: PuzzleYear,
day: PuzzleDay,
) -> Result<String, String> {
eprintln!("Downloading input for day {}, {}...", day, year);

let url = format!("https://adventofcode.com/{}/day/{}/input", year, day);
build_client(session_cookie, "text/plain")?
.get(&url)
.send()
.and_then(|response| response.error_for_status())
.and_then(|response| response.text())
.map_err(|err| err.to_string())
}

fn save_file(
filename: &str,
overwrite: bool,
contents: &str,
) -> Result<(), String> {
let mut file = OpenOptions::new();
if overwrite {
file.create(true);
Expand All @@ -139,22 +167,39 @@ pub fn download_input(
file.write(true)
.open(filename)
.map_err(|err| format!("Failed to create file: {}", err))?
.write(puzzle_input.as_bytes())
.write(contents.as_bytes())
.map_err(|err| format!("Failed to write to file: {}", err))?;

Ok(())
}

pub fn download(args: &Args, session_cookie: &str) -> Result<(), String> {
let (year, day) = puzzle_year_day(args.year, args.day)?;

if !args.input_only {
let desc = get_description(session_cookie, year, day)?;
eprintln!("Saving puzzle description to \"{}\"...", args.puzzle_file);
save_file(&args.puzzle_file, args.overwrite, &parse_html(&desc))?;
}

if !args.description_only {
let input = get_input(session_cookie, year, day)?;
eprintln!("Saving puzzle input to \"{}\"...", args.input_file);
save_file(&args.input_file, args.overwrite, &input)?;
}

eprintln!("Done!");
Ok(())
}

pub fn submit_answer(
pub fn submit(
args: &Args,
session_cookie: &str,
opt_year: Option<PuzzleYear>,
opt_day: Option<PuzzleDay>,
col_width: usize,
part: &str,
answer: &str,
col_width: usize,
) -> Result<(), String> {
let (year, day) = puzzle_year_day(opt_year, opt_day)?;
let (year, day) = puzzle_year_day(args.year, args.day)?;

eprintln!(
"Submitting answer for part {}, day {}, {}...",
Expand Down Expand Up @@ -182,51 +227,13 @@ pub fn submit_answer(
Ok(())
}

pub fn read_puzzle(
pub fn read(
args: &Args,
session_cookie: &str,
opt_year: Option<PuzzleYear>,
opt_day: Option<PuzzleDay>,
col_width: usize,
filename: &str,
overwrite: bool,
) -> Result<(), String> {
let (year, day) = puzzle_year_day(opt_year, opt_day)?;

let url = format!("https://adventofcode.com/{}/day/{}", year, day);
let content_type = "text/html";
let response = build_client(session_cookie, content_type)?
.get(&url)
.send()
.and_then(|response| response.error_for_status())
.and_then(|response| response.text())
.map_err(|err| err.to_string())?;

let description = Regex::new(r"(?i)(?s)<main>(?P<main>.*)</main>")
.unwrap()
.captures(&response)
.ok_or("Failed to parse puzzle description page")?
.name("main")
.unwrap()
.as_str();

println!("\n{}", from_read(description.as_bytes(), col_width));

println!("Saving puzzle description to \"{}\"...", filename);

let mut file = OpenOptions::new();
if overwrite {
file.create(true);
} else {
file.create_new(true);
};

file.write(true)
.open(filename)
.map_err(|err| format!("Failed to create file: {}", err))?
.write(parse_html(description).as_bytes())
.map_err(|err| format!("Failed to write to file: {}", err))?;

println!("Done!");

let (year, day) = puzzle_year_day(args.year, args.day)?;
let desc = get_description(session_cookie, year, day)?;
println!("\n{}", from_read(desc.as_bytes(), col_width));
Ok(())
}
12 changes: 10 additions & 2 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,18 @@ pub struct Args {
#[arg(short, long, global = true, value_parser = valid_width)]
pub width: Option<usize>,

/// Overwrite file if it already exists
/// Overwrite files if they already exist
#[arg(short, long, global = true)]
pub overwrite: bool,

/// Download puzzle input only
#[arg(short = 'I', long, global = true)]
pub input_only: bool,

/// Download puzzle description only
#[arg(short = 'D', long, global = true, conflicts_with = "input_only")]
pub description_only: bool,

/// Path where to save puzzle input
#[arg(
short,
Expand Down Expand Up @@ -58,7 +66,7 @@ pub enum Command {
#[command(visible_alias = "r")]
Read,

/// Download puzzle input
/// Save puzzle description and input to files
#[command(visible_alias = "d")]
Download,

Expand Down
Loading

0 comments on commit 57b9e37

Please sign in to comment.