321 lines
13 KiB
Rust
321 lines
13 KiB
Rust
use std::io::{Error, Result as IOResult, Write};
|
|
|
|
use crate::elements::{Clock, Element, Table, Timestamp};
|
|
use crate::export::write_datetime;
|
|
|
|
pub trait OrgHandler<E: From<Error>>: Default {
|
|
fn start<W: Write>(&mut self, w: W, element: &Element) -> Result<(), E>;
|
|
fn end<W: Write>(&mut self, w: W, element: &Element) -> Result<(), E>;
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct DefaultOrgHandler;
|
|
|
|
impl OrgHandler<Error> for DefaultOrgHandler {
|
|
fn start<W: Write>(&mut self, mut w: W, element: &Element) -> IOResult<()> {
|
|
match element {
|
|
// container elements
|
|
Element::SpecialBlock(block) => {
|
|
writeln!(w, "#+BEGIN_{}", block.name)?;
|
|
write_blank_lines(&mut w, block.pre_blank)?;
|
|
}
|
|
Element::QuoteBlock(block) => {
|
|
writeln!(&mut w, "#+BEGIN_QUOTE")?;
|
|
write_blank_lines(&mut w, block.pre_blank)?;
|
|
}
|
|
Element::CenterBlock(block) => {
|
|
writeln!(&mut w, "#+BEGIN_CENTER")?;
|
|
write_blank_lines(&mut w, block.pre_blank)?;
|
|
}
|
|
Element::VerseBlock(block) => {
|
|
writeln!(&mut w, "#+BEGIN_VERSE")?;
|
|
write_blank_lines(&mut w, block.pre_blank)?;
|
|
}
|
|
Element::Bold => write!(w, "*")?,
|
|
Element::Document { pre_blank } => {
|
|
write_blank_lines(w, *pre_blank)?;
|
|
}
|
|
Element::DynBlock(dyn_block) => {
|
|
write!(&mut w, "#+BEGIN: {}", dyn_block.block_name)?;
|
|
if let Some(parameters) = &dyn_block.arguments {
|
|
write!(&mut w, " {}", parameters)?;
|
|
}
|
|
write_blank_lines(&mut w, dyn_block.pre_blank + 1)?;
|
|
}
|
|
Element::Headline { .. } => (),
|
|
Element::List(_list) => (),
|
|
Element::Italic => write!(w, "/")?,
|
|
Element::ListItem(list_item) => {
|
|
for _ in 0..list_item.indent {
|
|
write!(&mut w, " ")?;
|
|
}
|
|
write!(&mut w, "{}", list_item.bullet)?;
|
|
}
|
|
Element::Paragraph { .. } => (),
|
|
Element::Section => (),
|
|
Element::Strike => write!(w, "+")?,
|
|
Element::Underline => write!(w, "_")?,
|
|
Element::Drawer(drawer) => {
|
|
writeln!(&mut w, ":{}:", drawer.name)?;
|
|
write_blank_lines(&mut w, drawer.pre_blank)?;
|
|
}
|
|
// non-container elements
|
|
Element::CommentBlock(block) => {
|
|
writeln!(&mut w, "#+BEGIN_COMMENT")?;
|
|
write!(&mut w, "{}", block.contents)?;
|
|
writeln!(&mut w, "#+END_COMMENT")?;
|
|
write_blank_lines(&mut w, block.post_blank)?;
|
|
}
|
|
Element::ExampleBlock(block) => {
|
|
writeln!(&mut w, "#+BEGIN_EXAMPLE")?;
|
|
write!(&mut w, "{}", block.contents)?;
|
|
writeln!(&mut w, "#+END_EXAMPLE")?;
|
|
write_blank_lines(&mut w, block.post_blank)?;
|
|
}
|
|
Element::ExportBlock(block) => {
|
|
writeln!(&mut w, "#+BEGIN_EXPORT {}", block.data)?;
|
|
write!(&mut w, "{}", block.contents)?;
|
|
writeln!(&mut w, "#+END_EXPORT")?;
|
|
write_blank_lines(&mut w, block.post_blank)?;
|
|
}
|
|
Element::SourceBlock(block) => {
|
|
writeln!(&mut w, "#+BEGIN_SRC {}", block.language)?;
|
|
write!(&mut w, "{}", block.contents)?;
|
|
writeln!(&mut w, "#+END_SRC")?;
|
|
write_blank_lines(&mut w, block.post_blank)?;
|
|
}
|
|
Element::BabelCall(call) => {
|
|
writeln!(&mut w, "#+CALL: {}", call.value)?;
|
|
write_blank_lines(w, call.post_blank)?;
|
|
}
|
|
Element::InlineSrc(inline_src) => {
|
|
write!(&mut w, "src_{}", inline_src.lang)?;
|
|
if let Some(options) = &inline_src.options {
|
|
write!(&mut w, "[{}]", options)?;
|
|
}
|
|
write!(&mut w, "{{{}}}", inline_src.body)?;
|
|
}
|
|
Element::Code { value } => write!(w, "~{}~", value)?,
|
|
Element::FnRef(fn_ref) => {
|
|
write!(&mut w, "[fn:{}", fn_ref.label)?;
|
|
if let Some(definition) = &fn_ref.definition {
|
|
write!(&mut w, ":{}", definition)?;
|
|
}
|
|
write!(&mut w, "]")?;
|
|
}
|
|
Element::InlineCall(inline_call) => {
|
|
write!(&mut w, "call_{}", inline_call.name)?;
|
|
if let Some(header) = &inline_call.inside_header {
|
|
write!(&mut w, "[{}]", header)?;
|
|
}
|
|
write!(&mut w, "({})", inline_call.arguments)?;
|
|
if let Some(header) = &inline_call.end_header {
|
|
write!(&mut w, "[{}]", header)?;
|
|
}
|
|
}
|
|
Element::Link(link) => {
|
|
write!(&mut w, "[[{}]", link.path)?;
|
|
if let Some(desc) = &link.desc {
|
|
write!(&mut w, "[{}]", desc)?;
|
|
}
|
|
write!(&mut w, "]")?;
|
|
}
|
|
Element::Macros(_macros) => (),
|
|
Element::RadioTarget => (),
|
|
Element::Snippet(snippet) => write!(w, "@@{}:{}@@", snippet.name, snippet.value)?,
|
|
Element::Target(_target) => (),
|
|
Element::Text { value } => write!(w, "{}", value)?,
|
|
Element::Timestamp(timestamp) => {
|
|
write_timestamp(&mut w, ×tamp)?;
|
|
}
|
|
Element::Verbatim { value } => write!(w, "={}=", value)?,
|
|
Element::FnDef(fn_def) => {
|
|
write_blank_lines(w, fn_def.post_blank)?;
|
|
}
|
|
Element::Clock(clock) => {
|
|
write!(w, "CLOCK: ")?;
|
|
|
|
match clock {
|
|
Clock::Closed {
|
|
start,
|
|
end,
|
|
duration,
|
|
post_blank,
|
|
..
|
|
} => {
|
|
write_datetime(&mut w, "[", &start, "]--")?;
|
|
write_datetime(&mut w, "[", &end, "]")?;
|
|
writeln!(&mut w, " => {}", duration)?;
|
|
write_blank_lines(&mut w, *post_blank)?;
|
|
}
|
|
Clock::Running {
|
|
start, post_blank, ..
|
|
} => {
|
|
write_datetime(&mut w, "[", &start, "]\n")?;
|
|
write_blank_lines(&mut w, *post_blank)?;
|
|
}
|
|
}
|
|
}
|
|
Element::Comment(comment) => {
|
|
write!(w, "{}", comment.value)?;
|
|
write_blank_lines(&mut w, comment.post_blank)?;
|
|
}
|
|
Element::FixedWidth(fixed_width) => {
|
|
write!(&mut w, "{}", fixed_width.value)?;
|
|
write_blank_lines(&mut w, fixed_width.post_blank)?;
|
|
}
|
|
Element::Keyword(keyword) => {
|
|
write!(&mut w, "#+{}", keyword.key)?;
|
|
if let Some(optional) = &keyword.optional {
|
|
write!(&mut w, "[{}]", optional)?;
|
|
}
|
|
writeln!(&mut w, ": {}", keyword.value)?;
|
|
write_blank_lines(&mut w, keyword.post_blank)?;
|
|
}
|
|
Element::Rule(rule) => {
|
|
writeln!(w, "-----")?;
|
|
write_blank_lines(&mut w, rule.post_blank)?;
|
|
}
|
|
Element::Cookie(_cookie) => (),
|
|
Element::Title(title) => {
|
|
for _ in 0..title.level {
|
|
write!(&mut w, "*")?;
|
|
}
|
|
if let Some(keyword) = &title.keyword {
|
|
write!(&mut w, " {}", keyword)?;
|
|
}
|
|
if let Some(priority) = title.priority {
|
|
write!(&mut w, " [#{}]", priority)?;
|
|
}
|
|
write!(&mut w, " ")?;
|
|
}
|
|
Element::Table(_) => (),
|
|
Element::TableRow(_) => (),
|
|
Element::TableCell(_) => (),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn end<W: Write>(&mut self, mut w: W, element: &Element) -> IOResult<()> {
|
|
match element {
|
|
// container elements
|
|
Element::SpecialBlock(block) => {
|
|
writeln!(&mut w, "#+END_{}", block.name)?;
|
|
write_blank_lines(&mut w, block.post_blank)?;
|
|
}
|
|
Element::QuoteBlock(block) => {
|
|
writeln!(&mut w, "#+END_QUOTE")?;
|
|
write_blank_lines(&mut w, block.post_blank)?;
|
|
}
|
|
Element::CenterBlock(block) => {
|
|
writeln!(&mut w, "#+END_CENTER")?;
|
|
write_blank_lines(&mut w, block.post_blank)?;
|
|
}
|
|
Element::VerseBlock(block) => {
|
|
writeln!(&mut w, "#+END_VERSE")?;
|
|
write_blank_lines(&mut w, block.post_blank)?;
|
|
}
|
|
Element::Bold => write!(w, "*")?,
|
|
Element::Document { .. } => (),
|
|
Element::DynBlock(dyn_block) => {
|
|
writeln!(w, "#+END:")?;
|
|
write_blank_lines(w, dyn_block.post_blank)?;
|
|
}
|
|
Element::Headline { .. } => (),
|
|
Element::List(list) => {
|
|
write_blank_lines(w, list.post_blank)?;
|
|
}
|
|
Element::Italic => write!(w, "/")?,
|
|
Element::ListItem(_) => (),
|
|
Element::Paragraph { post_blank } => {
|
|
write_blank_lines(w, post_blank + 1)?;
|
|
}
|
|
Element::Section => (),
|
|
Element::Strike => write!(w, "+")?,
|
|
Element::Underline => write!(w, "_")?,
|
|
Element::Drawer(drawer) => {
|
|
writeln!(&mut w, ":END:")?;
|
|
write_blank_lines(&mut w, drawer.post_blank)?;
|
|
}
|
|
Element::Title(title) => {
|
|
if !title.tags.is_empty() {
|
|
write!(&mut w, " :")?;
|
|
for tag in &title.tags {
|
|
write!(&mut w, "{}:", tag)?;
|
|
}
|
|
}
|
|
writeln!(&mut w)?;
|
|
if let Some(planning) = &title.planning {
|
|
if let Some(scheduled) = &planning.scheduled {
|
|
write!(&mut w, "SCHEDULED: ")?;
|
|
write_timestamp(&mut w, &scheduled)?;
|
|
}
|
|
if let Some(deadline) = &planning.deadline {
|
|
if planning.scheduled.is_some() {
|
|
write!(&mut w, " ")?;
|
|
}
|
|
write!(&mut w, "DEADLINE: ")?;
|
|
write_timestamp(&mut w, &deadline)?;
|
|
}
|
|
if let Some(closed) = &planning.closed {
|
|
if planning.deadline.is_some() {
|
|
write!(&mut w, " ")?;
|
|
}
|
|
write!(&mut w, "CLOSED: ")?;
|
|
write_timestamp(&mut w, &closed)?;
|
|
}
|
|
writeln!(&mut w)?;
|
|
}
|
|
if !title.properties.is_empty() {
|
|
writeln!(&mut w, ":PROPERTIES:")?;
|
|
for (key, value) in &title.properties {
|
|
writeln!(&mut w, ":{}: {}", key, value)?;
|
|
}
|
|
writeln!(&mut w, ":END:")?;
|
|
}
|
|
write_blank_lines(&mut w, title.post_blank)?;
|
|
}
|
|
Element::Table(Table::Org { post_blank, .. }) => {
|
|
write_blank_lines(w, *post_blank)?;
|
|
}
|
|
Element::Table(Table::TableEl { post_blank, .. }) => {
|
|
write_blank_lines(w, *post_blank)?;
|
|
}
|
|
Element::TableRow(_) => (),
|
|
Element::TableCell(_) => (),
|
|
// non-container elements
|
|
_ => debug_assert!(!element.is_container()),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn write_blank_lines<W: Write>(mut w: W, count: usize) -> Result<(), Error> {
|
|
for _ in 0..count {
|
|
writeln!(w)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn write_timestamp<W: Write>(mut w: W, timestamp: &Timestamp) -> Result<(), Error> {
|
|
match timestamp {
|
|
Timestamp::Active { start, .. } => {
|
|
write_datetime(w, "<", start, ">")?;
|
|
}
|
|
Timestamp::Inactive { start, .. } => {
|
|
write_datetime(w, "[", start, "]")?;
|
|
}
|
|
Timestamp::ActiveRange { start, end, .. } => {
|
|
write_datetime(&mut w, "<", start, ">--")?;
|
|
write_datetime(&mut w, "<", end, ">")?;
|
|
}
|
|
Timestamp::InactiveRange { start, end, .. } => {
|
|
write_datetime(&mut w, "[", start, "]--")?;
|
|
write_datetime(&mut w, "[", end, "]")?;
|
|
}
|
|
Timestamp::Diary { value } => write!(w, "<%%({})>", value)?,
|
|
}
|
|
Ok(())
|
|
}
|