| mod parser; | |
| use std::io::Read; | |
| use winnow::error::ContextError; | |
| use winnow::error::ErrMode; | |
| use winnow::error::Needed; | |
| use winnow::prelude::*; | |
| use winnow::stream::Offset; | |
| use winnow::stream::Stream as _; | |
| fn main() -> Result<(), lexopt::Error> { | |
| let args = Args::parse()?; | |
| let input = args.input.ok_or_else(|| lexopt::Error::MissingValue { | |
| option: Some("<PATH>".to_owned()), | |
| })?; | |
| let mut file = std::fs::File::open(input).map_err(to_lexopt)?; | |
| // Intentionally starting with a small buffer to make it easier to show `Incomplete` handling | |
| let buffer_size = 10; | |
| let min_buffer_growth = 100; | |
| let buffer_growth_factor = 2; | |
| let mut buffer = circular::Buffer::with_capacity(buffer_size); | |
| loop { | |
| let read = file.read(buffer.space()).map_err(to_lexopt)?; | |
| eprintln!("read {read}"); | |
| if read == 0 { | |
| // Should be EOF since we always make sure there is `available_space` | |
| assert_ne!(buffer.available_space(), 0); | |
| assert_eq!( | |
| buffer.available_data(), | |
| 0, | |
| "leftover data: {}", | |
| String::from_utf8_lossy(buffer.data()) | |
| ); | |
| break; | |
| } | |
| buffer.fill(read); | |
| loop { | |
| let mut input = | |
| parser::Stream::new(std::str::from_utf8(buffer.data()).map_err(to_lexopt)?); | |
| let start = input.checkpoint(); | |
| match parser::ndjson::<ContextError>.parse_next(&mut input) { | |
| Ok(value) => { | |
| println!("{value:?}"); | |
| println!(); | |
| // Tell the buffer how much we read | |
| let consumed = input.offset_from(&start); | |
| buffer.consume(consumed); | |
| } | |
| Err(ErrMode::Backtrack(e)) | Err(ErrMode::Cut(e)) => { | |
| return Err(fmt_lexopt(e.to_string())); | |
| } | |
| Err(ErrMode::Incomplete(Needed::Size(size))) => { | |
| // Without the format telling us how much space is required, we really should | |
| // treat this the same as `Unknown` but are doing this to demonstrate how to | |
| // handle `Size`. | |
| // | |
| // Even when the format has a header to tell us `Size`, we could hit incidental | |
| // `Size(1)`s, so make sure we buffer more space than that to avoid reading | |
| // one byte at a time | |
| let head_room = size.get().max(min_buffer_growth); | |
| let new_capacity = buffer.available_data() + head_room; | |
| eprintln!("growing buffer to {new_capacity}"); | |
| buffer.grow(new_capacity); | |
| if buffer.available_space() < head_room { | |
| eprintln!("buffer shift"); | |
| buffer.shift(); | |
| } | |
| break; | |
| } | |
| Err(ErrMode::Incomplete(Needed::Unknown)) => { | |
| let new_capacity = buffer_growth_factor * buffer.capacity(); | |
| eprintln!("growing buffer to {new_capacity}"); | |
| buffer.grow(new_capacity); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| Ok(()) | |
| } | |
| #[derive(Default)] | |
| struct Args { | |
| input: Option<std::path::PathBuf>, | |
| } | |
| impl Args { | |
| fn parse() -> Result<Self, lexopt::Error> { | |
| use lexopt::prelude::*; | |
| let mut res = Args::default(); | |
| let mut args = lexopt::Parser::from_env(); | |
| while let Some(arg) = args.next()? { | |
| match arg { | |
| Value(input) => { | |
| res.input = Some(input.into()); | |
| } | |
| _ => return Err(arg.unexpected()), | |
| } | |
| } | |
| Ok(res) | |
| } | |
| } | |
| fn to_lexopt(e: impl std::error::Error + Send + Sync + 'static) -> lexopt::Error { | |
| lexopt::Error::Custom(Box::new(e)) | |
| } | |
| fn fmt_lexopt(e: String) -> lexopt::Error { | |
| lexopt::Error::Custom(e.into()) | |
| } |