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:#?}"), // TODOMedia::Text(s) => println!("{s}"),}}}#[derive(Clone, Debug)]pub struct Gemini<'a> {// TODO include lang componentlines: 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 linesreturn None;} else {// ignore any other formatting between preformat toggle linesreturn 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 linesreturn 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 modeNone => {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 modeRawLine::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 inputrpassword::read_password()} else {// Read plain inputstd::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:#?}"), // TODOMedia::Text(s) => println!("{s}"),}}}#[derive(Clone, Debug)]pub struct Gemini<'a> {// TODO include lang componentlines: 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 linesreturn None;} else {// ignore any other formatting between preformat toggle linesreturn 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 linesreturn 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 modeNone => {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 modeRawLine::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);}}