Skip to content

Instantly share code, notes, and snippets.

@passcod
Last active January 6, 2024 23:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save passcod/9148782657f3f40df75ff85b8dd77c2a to your computer and use it in GitHub Desktop.
Save passcod/9148782657f3f40df75ff85b8dd77c2a to your computer and use it in GitHub Desktop.
//! Parse ini with deku
//!
//! You need this https://github.com/sharksforarms/deku/pull/387 branch of deku for it to work.
//!
//! See https://twitter.com/passcod/status/1743186223940935754
use deku::prelude::*;
use std::fs::read_to_string;
fn main() {
tracing_subscriber::fmt::init();
let source = read_to_string("super/python-http.service").unwrap();
let (_rest, unit) = Unit::from_bytes((source.as_bytes(), 0)).unwrap();
println!("{:#?}", unit);
}
#[deku_derive(DekuRead)]
#[derive(Debug)]
struct Unit {
#[deku(temp, until = "|b: &u8| *b == b'['")]
head_ws: Vec<u8>,
#[deku(read_all)]
sections: Vec<Section>,
}
#[deku_derive(DekuRead)]
#[derive(Debug)]
struct Section {
#[deku(
until = "|b: &u8| *b == b']'",
map = "|bytes: Vec<u8>| -> Result<String, DekuError> { deku_bytes_to_string(bytes).map(clean(']')).map(normalise_case) }"
)]
name: String,
#[deku(
until = "|p: &MaybeProperty| p.property.is_none()",
map = "MaybeProperty::finalise_all"
)]
properties: Vec<Property>,
}
impl Section {
fn last(&self) -> bool {
todo!()
}
}
#[deku_derive(DekuRead)]
#[derive(Debug)]
struct MaybeProperty {
#[deku(until = "|b: &u8| *b != b'\\n'")]
ws: VecTilEnd<u8>,
#[deku(cond = "ws.0.last().map_or(false, |c| c != &b'[')")]
property: Option<Property>,
}
impl MaybeProperty {
fn finalise(self) -> Option<Property> {
if let Some(mut property) = self.property {
if let Some(c) = self.ws.0.last().map(|b| *b as char) {
property.key.insert(0, c);
}
Some(property)
} else {
None
}
}
fn finalise_all(mpv: Vec<MaybeProperty>) -> Result<Vec<Property>, DekuError> {
Ok(mpv.into_iter().filter_map(Self::finalise).collect())
}
}
#[deku_derive(DekuRead)]
#[derive(Debug)]
struct Property {
#[deku(until = "|b: &u8| *b == b'='", map = "cleaned_string('=')")]
key: String,
#[deku(until = "|b: &u8| *b == b'\\n'", map = "cleaned_string('\\n')")]
value: String,
}
fn cleaned_string(end: char) -> impl Fn(Vec<u8>) -> Result<String, DekuError> {
move |bytes: Vec<u8>| -> Result<String, DekuError> {
deku_bytes_to_string(bytes).map(clean(end))
}
}
fn deku_bytes_to_string(bytes: Vec<u8>) -> Result<String, DekuError> {
String::from_utf8(bytes).map_err(|e| DekuError::Parse(format!("{e}")))
}
fn clean(end: char) -> impl Fn(String) -> String {
move |bytes: String| -> String {
bytes
.trim_start()
.trim_end()
.trim_end_matches(end)
.to_string()
}
}
fn normalise_case(string: String) -> String {
let mut lower = string.to_lowercase();
lower.replace_range(0..1, &string[0..1].to_uppercase());
lower
}
#[derive(Debug)]
struct VecTilEnd<T>(Vec<T>);
impl<'a, T, Ctx, Predicate> DekuReader<'a, (deku::ctx::Limit<T, Predicate>, Ctx)> for VecTilEnd<T>
where
T: DekuReader<'a, Ctx>,
Ctx: Copy,
Predicate: FnMut(&T) -> bool,
{
fn from_reader_with_ctx<R: std::io::Read>(
reader: &mut deku::reader::Reader<R>,
(limit, inner_ctx): (deku::ctx::Limit<T, Predicate>, Ctx),
) -> Result<Self, DekuError>
where
Self: Sized,
{
let deku::ctx::Limit::Until(mut predicate, _) = limit else {
return Err(DekuError::Parse("`until` is required here".to_string()));
};
let mut res = Vec::new();
let start_read = reader.bits_read;
loop {
if reader.end() {
break;
}
let val = <T>::from_reader_with_ctx(reader, inner_ctx)?;
res.push(val);
if predicate(res.last().unwrap()) {
break;
}
}
Ok(Self(res))
}
}
[Unit]
Description=Python HTTP static file server
After=network.target
[Service]
ExecStart=/usr/bin/python3 -mhttp.server
[Install]
WantedBy=multi-user.target
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment