WGRFJRTEXOKY7PV536O62WUE32T24VQEHFQP67GEZZ67AE37ZWUAC
OSV63NXNIRGOEJZUECHXYYYEFUAFXLN4BNIH3MKLE7SDZLHGFEWAC
3SPNKI46URBW4PD3JRMBV5YV5W56N3SFUTK25VQEF2FUZ64ZDQPAC
DBKKKHC2ER7XAYR6HAFQ6I75M3OLDOSGCB5BTEP7X6XAZRSWKIRAC
AOJXTWBZA2DQBFTJZX2YTGAKIB62SKTEVJ7RRIBJNDSMC75DDQVQC
AMTMTTJTF2XHEUC6ACP2PHP7GAFMFZRSJ47VJBKEO22X4ZGFIX7QC
ONQEIR5BV26QJSI3HY7B3MN6VWEGSZIXAIUIANJ5ZLR3QN3DFGEAC
HVFBGX2A2LBL6HSFKPNIG5KGQPVMZ5GFYJ4ZPDAM4POSRTQSKN7QC
1 => Some(Status::Input((sub, meta).into())),
2 => Some(Status::Success((sub, meta).into())),
3 => Some(Status::Redirect((sub, meta).into())),
4 => Some(Status::FailTemp((sub, meta).into())),
5 => Some(Status::FailPerm((sub, meta).into())),
6 => Some(Status::CertRequired((sub, meta).into())),
1 => Some(Header::Input((sub, meta).into())),
2 => Some(Header::Success((sub, meta).into())),
3 => Some(Header::Redirect((sub, meta).into())),
4 => Some(Header::FailTemp((sub, meta).into())),
5 => Some(Header::FailPerm((sub, meta).into())),
6 => Some(Header::CertRequired((sub, meta).into())),
use std::str::pattern::Pattern;
use url::Url;
#[derive(Clone, Debug)]
pub enum Media<'a> {
Gemini(Gemini<'a>),
Text(&'a str),
}
impl<'a> Media<'a> {
pub fn display(&self) {
match self {
Media::Gemini(g) => println!("{g:#?}"), // TODO
Media::Text(s) => println!("{s}"),
}
}
}
#[derive(Clone, Debug)]
pub struct Gemini<'a> {
// TODO include lang component
lines: Vec<Line<'a>>,
}
#[derive(Clone, Debug)]
enum RawLine<'a> {
Toggle { alt: TextLine<'a> },
Line(Line<'a>),
}
#[derive(Clone, Debug)]
pub enum Line<'a> {
Heading {
level: u8,
title: TextLine<'a>,
},
Link {
url: TextLine<'a>,
description: Option<TextLine<'a>>,
},
Text(TextLine<'a>),
Preformatted(Preformat<'a>),
ListItem(TextLine<'a>),
Quote(TextLine<'a>),
}
#[derive(Clone, Debug)]
pub struct Preformat<'a> {
alt: TextLine<'a>,
lines: Vec<TextLine<'a>>,
}
fn split_trim_maybe_once<'a, P: Pattern<'a> + Copy>(
s: &'a str,
p: P,
) -> (&'a str, Option<&'a str>) {
match s.split_once(p) {
Some((s, rem)) => (s, Some(rem.trim_start_matches(p))),
None => (s, None),
}
}
fn string_to_preformat(string: TextLine<'_>) -> Option<TextLine<'_>> {
let line = string.0;
if line.starts_with("```") {
// ignore anything after the lead chars on preformat lines
return None;
} else {
// ignore any other formatting between preformat toggle lines
return Some(string);
}
}
fn string_to_line(string: TextLine<'_>) -> RawLine {
let line = string.0;
RawLine::Line({
if line.starts_with("```") {
// ignore anything after the lead chars on preformat lines
return RawLine::Toggle {
alt: TextLine(&line[3..]),
};
} else if line.starts_with("=> ") {
let line = &line[3..];
match line.split_once(' ') {
Some((url, desc)) => {
let url = url.trim_start_matches(' ');
Line::Link {
url: TextLine(url),
description: Some(TextLine(desc)),
}
}
None => Line::Link {
url: TextLine(line),
description: None,
},
}
} else if line.starts_with("* ") {
Line::ListItem(TextLine(&line[2..]))
} else if line.starts_with("# ") {
Line::Heading {
level: 1,
title: TextLine(&line[2..]),
}
} else if line.starts_with("## ") {
Line::Heading {
level: 2,
title: TextLine(&line[3..]),
}
} else if line.starts_with("### ") {
Line::Heading {
level: 3,
title: TextLine(&line[4..]),
}
} else if line.starts_with(">") {
Line::Quote(TextLine(&line[1..]))
} else {
Line::Text(string)
}
})
}
fn string_to_lines(value: &str) -> Vec<Line> {
{
let mut outer = vec![];
let mut preformat_block: Option<Preformat> = None;
let lines = value.lines().map(TextLine);
for line in lines {
if preformat_block.is_some() {
match string_to_preformat(line) {
// if we hit a toggle line, switch preformatting mode
None => {
let Some(i) = preformat_block.take()
else {unreachable!("This is within the is_some arm of the if")};
outer.push(Line::Preformatted(i));
}
Some(p) => preformat_block.as_mut().unwrap().lines.push(p),
}
} else {
match string_to_line(line) {
// if we hit a toggle line, switch preformatting mode
RawLine::Toggle { alt } => {
preformat_block = Some(Preformat { alt, lines: vec![] });
}
RawLine::Line(l) => outer.push(l),
}
}
}
outer
}
}
// guaranteed to be only a single line
#[derive(Copy, Clone, Debug)]
pub struct TextLine<'a>(&'a str);
impl<'a> From<&'a str> for Gemini<'a> {
fn from(value: &'a str) -> Self {
Gemini {
lines: string_to_lines(value),
}
}
}
let input = match sensitive {
Some(true) => rpassword::read_password(),
_ => std::io::stdin().lines().next().expect("End of input"),
let input = if let Some(true) = sensitive {
// Read sensitive input
rpassword::read_password()
} else {
// Read plain input
std::io::stdin().lines().next().expect("End of input")
}
mod media {
use std::str::pattern::Pattern;
use url::Url;
#[derive(Clone, Debug)]
pub enum Media<'a> {
Gemini(Gemini<'a>),
Text(&'a str),
}
impl<'a> Media<'a> {
pub fn display(&self) {
match self {
Media::Gemini(g) => println!("{g:#?}"), // TODO
Media::Text(s) => println!("{s}"),
}
}
}
#[derive(Clone, Debug)]
pub struct Gemini<'a> {
// TODO include lang component
lines: Vec<Line<'a>>,
}
#[derive(Clone, Debug)]
enum RawLine<'a> {
Toggle { alt: TextLine<'a> },
Line(Line<'a>),
}
#[derive(Clone, Debug)]
pub enum Line<'a> {
Heading {
level: u8,
title: TextLine<'a>,
},
Link {
url: TextLine<'a>,
description: Option<TextLine<'a>>,
},
Text(TextLine<'a>),
Preformatted(Preformat<'a>),
ListItem(TextLine<'a>),
Quote(TextLine<'a>),
}
#[derive(Clone, Debug)]
pub struct Preformat<'a> {
alt: TextLine<'a>,
lines: Vec<TextLine<'a>>,
}
fn split_trim_maybe_once<'a, P: Pattern<'a> + Copy>(
s: &'a str,
p: P,
) -> (&'a str, Option<&'a str>) {
match s.split_once(p) {
Some((s, rem)) => (s, Some(rem.trim_start_matches(p))),
None => (s, None),
}
}
fn string_to_preformat(string: TextLine<'_>) -> Option<TextLine<'_>> {
let line = string.0;
if line.starts_with("```") {
// ignore anything after the lead chars on preformat lines
return None;
} else {
// ignore any other formatting between preformat toggle lines
return Some(string);
}
}
fn string_to_line(string: TextLine<'_>) -> RawLine {
let line = string.0;
RawLine::Line({
if line.starts_with("```") {
// ignore anything after the lead chars on preformat lines
return RawLine::Toggle {
alt: TextLine(&line[3..]),
};
} else if line.starts_with("=> ") {
let line = &line[3..];
match line.split_once(' ') {
Some((url, desc)) => {
let url = url.trim_start_matches(' ');
Line::Link {
url: TextLine(url),
description: Some(TextLine(desc)),
}
}
None => Line::Link {
url: TextLine(line),
description: None,
},
}
} else if line.starts_with("* ") {
Line::ListItem(TextLine(&line[2..]))
} else if line.starts_with("# ") {
Line::Heading {
level: 1,
title: TextLine(&line[2..]),
}
} else if line.starts_with("## ") {
Line::Heading {
level: 2,
title: TextLine(&line[3..]),
}
} else if line.starts_with("### ") {
Line::Heading {
level: 3,
title: TextLine(&line[4..]),
}
} else if line.starts_with(">") {
Line::Quote(TextLine(&line[1..]))
} else {
Line::Text(string)
}
})
}
fn string_to_lines(value: &str) -> Vec<Line> {
{
let mut outer = vec![];
let mut preformat_block: Option<Preformat> = None;
let lines = value.lines().map(TextLine);
for line in lines {
if preformat_block.is_some() {
match string_to_preformat(line) {
// if we hit a toggle line, switch preformatting mode
None => {
let Some(i) = preformat_block.take()
else {unreachable!("This is within the is_some arm of the if")};
outer.push(Line::Preformatted(i));
}
Some(p) => preformat_block.as_mut().unwrap().lines.push(p),
}
} else {
match string_to_line(line) {
// if we hit a toggle line, switch preformatting mode
RawLine::Toggle { alt } => {
preformat_block = Some(Preformat { alt, lines: vec![] });
}
RawLine::Line(l) => outer.push(l),
}
}
}
outer
}
}
// guaranteed to be only a single line
#[derive(Copy, Clone, Debug)]
pub struct TextLine<'a>(&'a str);
impl<'a> From<&'a str> for Gemini<'a> {
fn from(value: &'a str) -> Self {
Gemini {
lines: string_to_lines(value),
}
}
}
#[cfg(test)]
mod tests {
use url::Url;
use super::{
handle_response_header,
response::{header, Header},
};
#[test]
fn handle_redirect_none() {
handle_redirect("http://url.com".parse().unwrap(), None);
}
#[test]
fn handle_redirect_perm() {
handle_redirect("http://url.com".parse().unwrap(), Some(false));
}
#[test]
fn handle_redirect_temp() {
handle_redirect("http://url.com".parse().unwrap(), Some(true));
}
fn handle_redirect(expect_url: Url, temp: Option<bool>) {
let head = Header::Redirect(header::Redirect {
url: expect_url.clone(),
temporary: temp,
});
let url = "gopher://abc.de".parse().unwrap();
let mut stream: &[u8] = b"";
let Some(url) = handle_response_header(head, url, &mut stream)
else {panic!("expected redirect handling to return a url")};
assert_eq!(url, expect_url);
}
}