Skip to content

Commit

Permalink
Better separation of binary and library (#70)
Browse files Browse the repository at this point in the history
* Create internal module for logic that isn't intended to be published as part of the library

* Move all printing/formatting logic to the internal module

* Move PublicHandle from main.rs to library

* Make enum PuzzleType non-public

* Completely refactor solution module

* Move build command from solution to main.rs

* Handle solution command failing to run

* Add some docstrings for Clash

* Change `.expect()` messages to comply with [the convention](https://doc.rust-lang.org/std/result/enum.Result.html#recommended-message-style)

---------

Co-authored-by: ellnix <103502144+ellnix@users.noreply.github.com>
  • Loading branch information
Andriamanitra and ellnix authored May 7, 2024
1 parent 0210067 commit ced9db8
Show file tree
Hide file tree
Showing 13 changed files with 476 additions and 452 deletions.
63 changes: 7 additions & 56 deletions src/clash.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
use serde::{Deserialize, Serialize};

use crate::formatter::format_cg;
use crate::outputstyle::OutputStyle;

mod public_handle;
mod test_case;

pub use public_handle::PublicHandle;
use serde::{Deserialize, Serialize};
use test_case::deserialize_testcases;
pub use test_case::TestCase;

/// `Clash` represents a deserialized Clash of Code or I/O puzzle.
#[derive(Debug, Serialize, Deserialize)]
pub struct Clash {
id: u32,
#[serde(rename = "publicHandle")]
public_handle: String,
public_handle: PublicHandle,
#[serde(rename = "lastVersion")]
last_version: ClashVersion,
#[serde(rename = "type")]
Expand All @@ -23,7 +23,7 @@ pub struct Clash {
}

#[derive(Debug, Serialize, Deserialize)]
pub enum PuzzleType {
enum PuzzleType {
#[serde(rename = "CLASHOFCODE")]
Clash,
#[serde(rename = "PUZZLE_INOUT")]
Expand Down Expand Up @@ -113,53 +113,4 @@ impl Clash {
pub fn is_reverse_only(&self) -> bool {
self.is_reverse() && !self.is_fastest() && !self.is_shortest()
}

pub fn print_headers(&self, ostyle: &OutputStyle) {
println!("{}\n", ostyle.title.paint(format!("=== {} ===", self.title())));
println!("{}\n", ostyle.link.paint(self.codingame_link()));
}

pub fn print_statement(&self, ostyle: &OutputStyle) {
println!("{}\n", format_cg(self.statement(), ostyle));
println!("{}\n{}\n", ostyle.title.paint("Input:"), format_cg(self.input_description(), ostyle));
println!(
"{}\n{}\n",
ostyle.title.paint("Output:"),
format_cg(self.output_description(), ostyle)
);
if let Some(constraints) = self.constraints() {
println!("{}\n{}\n", ostyle.title.paint("Constraints:"), format_cg(constraints, ostyle));
}

let example = self.testcases().first().expect("no test cases");
println!(
"{}\n{}\n{}\n{}",
ostyle.title.paint("Example:"),
example.styled_input(ostyle),
ostyle.title.paint("Expected output:"),
example.styled_output(ostyle),
);
}

pub fn print_testcases(&self, ostyle: &OutputStyle, selection: Vec<usize>) {
// Skips validators: -t 1 will print the example, -t 2 will print the second
// test (skipping validator 1)
for (idx, testcase) in self.testcases().iter().filter(|t| !t.is_validator).enumerate() {
if selection.contains(&idx) {
println!(
"{}\n{}\n\n{}\n",
testcase.styled_title(ostyle),
testcase.styled_input(ostyle),
testcase.styled_output(ostyle),
);
}
}
}

pub fn print_reverse_mode(&self, ostyle: &OutputStyle) {
self.print_headers(ostyle);
println!("{}\n", ostyle.title.paint("REVERSE!"));
let selection = (0..self.testcases().len()).collect::<Vec<usize>>();
self.print_testcases(ostyle, selection);
}
}
49 changes: 49 additions & 0 deletions src/clash/public_handle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::str::FromStr;

use anyhow::anyhow;
use serde::{Deserialize, Deserializer, Serialize};

/// `PublicHandle` is a hexadecimal string that uniquely identifies a clash
/// or a puzzle. It is the last part of the URL when viewing a clash or a puzzle
/// on the CodinGame contribution page.
///
/// # Examples
///
/// ```
/// use clashlib::clash::PublicHandle;
/// use std::str::FromStr;
///
/// let handle = PublicHandle::from_str("682102420fbce0fce95e0ee56095ea2b9924");
/// assert!(handle.is_ok());
/// let invalid_handle = PublicHandle::from_str("xyz");
/// assert!(invalid_handle.is_err());
/// ```
#[derive(Debug, Clone, Serialize)]
pub struct PublicHandle(String);

impl FromStr for PublicHandle {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.chars().all(|ch| ch.is_ascii_hexdigit()) {
Ok(PublicHandle(String::from(s)))
} else {
Err(anyhow!("valid handles only contain characters 0-9 and a-f"))
}
}
}

impl std::fmt::Display for PublicHandle {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

impl<'de> Deserialize<'de> for PublicHandle {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
FromStr::from_str(&s).map_err(serde::de::Error::custom)
}
}
27 changes: 9 additions & 18 deletions src/clash/test_case.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
use serde::{Deserialize, Deserializer, Serialize};

use crate::formatter::show_whitespace;
use crate::outputstyle::OutputStyle;

/// `TestCase` is a deserialized representation of a test case for a Clash of
/// Code or I/O puzzle.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct TestCase {
/// `index` is the number of the test/validator, starting from 1.
#[serde(skip_serializing, skip_deserializing)]
index: usize,
pub index: usize,
/// `title` is a human readable name for the test/validator
#[serde(deserialize_with = "deserialize_testcase_title")]
pub title: String,
/// `test_in` is the input that a solution reads from STDIN
#[serde(rename = "testIn")]
pub test_in: String,
/// `test_out` is the output that a solution is expected to print to STDOUT
#[serde(rename = "testOut")]
pub test_out: String,
/// `is_validator` is true for test cases that are not normally visible when
/// solving a puzzle on CodinGame.
#[serde(rename = "isValidator")]
pub is_validator: bool,
}
Expand Down Expand Up @@ -45,17 +50,3 @@ fn deserialize_testcase_title<'de, D: Deserializer<'de>>(de: D) -> Result<String
};
Ok(title)
}

impl TestCase {
pub fn styled_title(&self, ostyle: &OutputStyle) -> String {
ostyle.title.paint(format!("#{} {}", self.index, self.title)).to_string()
}

pub fn styled_input(&self, ostyle: &OutputStyle) -> String {
show_whitespace(&self.test_in, &ostyle.input, &ostyle.input_whitespace)
}

pub fn styled_output(&self, ostyle: &OutputStyle) -> String {
show_whitespace(&self.test_out, &ostyle.output, &ostyle.output_whitespace)
}
}
5 changes: 5 additions & 0 deletions src/internal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod formatter;
mod lines_with_endings;
mod outputstyle;

pub use outputstyle::OutputStyle;
2 changes: 1 addition & 1 deletion src/formatter.rs → src/internal/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ansi_term::Style;
use lazy_static::lazy_static;
use regex::Regex;

use crate::outputstyle::OutputStyle;
use super::outputstyle::OutputStyle;

// use lazy_static! to make sure regexes are only compiled once
lazy_static! {
Expand Down
25 changes: 25 additions & 0 deletions src/internal/lines_with_endings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// https://stackoverflow.com/a/40457615/5465108
pub struct LinesWithEndings<'a> {
input: &'a str,
}

impl<'a> LinesWithEndings<'a> {
pub fn from(input: &'a str) -> LinesWithEndings<'a> {
LinesWithEndings { input }
}
}

impl<'a> Iterator for LinesWithEndings<'a> {
type Item = &'a str;

#[inline]
fn next(&mut self) -> Option<&'a str> {
if self.input.is_empty() {
return None
}
let split = self.input.find('\n').map(|i| i + 1).unwrap_or(self.input.len());
let (line, rest) = self.input.split_at(split);
self.input = rest;
Some(line)
}
}
Loading

0 comments on commit ced9db8

Please sign in to comment.