Skip to content

Commit

Permalink
Restructure document module
Browse files Browse the repository at this point in the history
  • Loading branch information
scouten committed Dec 30, 2023
1 parent c52f352 commit a75a436
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 181 deletions.
65 changes: 65 additions & 0 deletions src/document/document.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Describes the top-level document structure.

use std::slice::Iter;

use nom::IResult;

use crate::{blocks::Block, primitives::consume_empty_lines, Error, HasSpan, Span};

/// A document represents the top-level block element in AsciiDoc. It consists
/// of an optional document header and either a) one or more sections preceded
/// by an optional preamble or b) a sequence of top-level blocks only.
///
/// The document can be configured using a document header. The header is not a
/// block itself, but contributes metadata to the document, such as the document
/// title and document attributes.
#[allow(dead_code)] // TEMPORARY while building
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Document<'a> {
blocks: Vec<Block<'a>>,
source: Span<'a>,
}

impl<'a> Document<'a> {
/// Parse a UTF-8 string as an AsciiDoc document.
///
/// Note that the document references the underlying source string and
/// necessarily has the same lifetime as the source.
pub fn parse(source: &'a str) -> Result<Self, Error> {
let source = Span::new(source, true);
let i = source;

// TO DO: Look for document header.
// TO DO: Add option for best-guess parsing?

let (_rem, blocks) = parse_blocks(i)?;

// let blocks: Vec<Block<'a>> = vec![]; // TEMPORARY
Ok(Self { source, blocks })
}

/// Return an iterator over the blocks in this document.
pub fn blocks(&'a self) -> Iter<'a, Block<'a>> {
self.blocks.iter()
}
}

impl<'a> HasSpan<'a> for Document<'a> {
fn span(&'a self) -> &'a Span<'a> {
&self.source
}
}

fn parse_blocks<'a>(mut i: Span<'a>) -> IResult<Span, Vec<Block<'a>>> {
let mut blocks: Vec<Block<'a>> = vec![];
i = consume_empty_lines(i);

while !i.data().is_empty() {
// TO DO: Handle other kinds of blocks.
let (i2, block) = Block::parse(i)?;
i = i2;
blocks.push(block);
}

Ok((i, blocks))
}
69 changes: 6 additions & 63 deletions src/document/mod.rs
Original file line number Diff line number Diff line change
@@ -1,65 +1,8 @@
//! Describes the top-level document structure.

use std::slice::Iter;

use nom::IResult;

use crate::{blocks::Block, primitives::consume_empty_lines, Error, HasSpan, Span};

/// A document represents the top-level block element in AsciiDoc. It consists
/// of an optional document header and either a) one or more sections preceded
/// by an optional preamble or b) a sequence of top-level blocks only.
///
/// The document can be configured using a document header. The header is not a
/// block itself, but contributes metadata to the document, such as the document
/// title and document attributes.
#[allow(dead_code)] // TEMPORARY while building
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Document<'a> {
blocks: Vec<Block<'a>>,
source: Span<'a>,
}

impl<'a> Document<'a> {
/// Parse a UTF-8 string as an AsciiDoc document.
///
/// Note that the document references the underlying source string and
/// necessarily has the same lifetime as the source.
pub fn parse(source: &'a str) -> Result<Self, Error> {
let source = Span::new(source, true);
let i = source;

// TO DO: Look for document header.
// TO DO: Add option for best-guess parsing?

let (_rem, blocks) = parse_blocks(i)?;

// let blocks: Vec<Block<'a>> = vec![]; // TEMPORARY
Ok(Self { source, blocks })
}

/// Return an iterator over the blocks in this document.
pub fn blocks(&'a self) -> Iter<'a, Block<'a>> {
self.blocks.iter()
}
}

impl<'a> HasSpan<'a> for Document<'a> {
fn span(&'a self) -> &'a Span<'a> {
&self.source
}
}

fn parse_blocks<'a>(mut i: Span<'a>) -> IResult<Span, Vec<Block<'a>>> {
let mut blocks: Vec<Block<'a>> = vec![];
i = consume_empty_lines(i);

while !i.data().is_empty() {
// TO DO: Handle other kinds of blocks.
let (i2, block) = Block::parse(i)?;
i = i2;
blocks.push(block);
}

Ok((i, blocks))
}
// I understand the purpose behind this warning, but
// this module/submodule layout feels preferable in this
// circumstance.
#[allow(clippy::module_inception)]
mod document;
pub use document::Document;
116 changes: 116 additions & 0 deletions src/tests/document/document.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use pretty_assertions_sorted::assert_eq;

use crate::{
document::Document,
tests::fixtures::{
blocks::{TBlock, TSimpleBlock},
document::TDocument,
TSpan,
},
};

#[test]
fn empty_source() {
assert_eq!(
Document::parse("").unwrap(),
TDocument {
source: TSpan {
data: "",
line: 1,
col: 1,
offset: 0
},
blocks: vec![],
}
);
}

#[test]
fn only_spaces() {
assert_eq!(
Document::parse(" ").unwrap(),
TDocument {
source: TSpan {
data: " ",
line: 1,
col: 1,
offset: 0
},
blocks: vec![],
}
);
}

#[test]
fn one_simple_block() {
assert_eq!(
Document::parse("abc").unwrap(),
TDocument {
source: TSpan {
data: "abc",
line: 1,
col: 1,
offset: 0
},
blocks: vec![TBlock::Simple(TSimpleBlock {
inlines: vec![TSpan {
data: "abc",
line: 1,
col: 1,
offset: 0,
},],
source: TSpan {
data: "abc",
line: 1,
col: 1,
offset: 0,
}
})],
}
);
}

#[test]
fn two_simple_blocks() {
assert_eq!(
Document::parse("abc\n\ndef").unwrap(),
TDocument {
source: TSpan {
data: "abc\n\ndef",
line: 1,
col: 1,
offset: 0
},
blocks: vec![
TBlock::Simple(TSimpleBlock {
inlines: vec![TSpan {
data: "abc",
line: 1,
col: 1,
offset: 0,
},],
source: TSpan {
data: "abc\n",
line: 1,
col: 1,
offset: 0,
}
}),
TBlock::Simple(TSimpleBlock {
inlines: vec![TSpan {
data: "def",
line: 3,
col: 1,
offset: 5,
},],
source: TSpan {
data: "def",
line: 3,
col: 1,
offset: 5,
}
}),
],
}
);
}
121 changes: 5 additions & 116 deletions src/tests/document/mod.rs
Original file line number Diff line number Diff line change
@@ -1,116 +1,5 @@
use pretty_assertions_sorted::assert_eq;

use crate::{
document::Document,
tests::fixtures::{
blocks::{TBlock, TSimpleBlock},
document::TDocument,
TSpan,
},
};

#[test]
fn empty_source() {
assert_eq!(
Document::parse("").unwrap(),
TDocument {
source: TSpan {
data: "",
line: 1,
col: 1,
offset: 0
},
blocks: vec![],
}
);
}

#[test]
fn only_spaces() {
assert_eq!(
Document::parse(" ").unwrap(),
TDocument {
source: TSpan {
data: " ",
line: 1,
col: 1,
offset: 0
},
blocks: vec![],
}
);
}

#[test]
fn one_simple_block() {
assert_eq!(
Document::parse("abc").unwrap(),
TDocument {
source: TSpan {
data: "abc",
line: 1,
col: 1,
offset: 0
},
blocks: vec![TBlock::Simple(TSimpleBlock {
inlines: vec![TSpan {
data: "abc",
line: 1,
col: 1,
offset: 0,
},],
source: TSpan {
data: "abc",
line: 1,
col: 1,
offset: 0,
}
})],
}
);
}

#[test]
fn two_simple_blocks() {
assert_eq!(
Document::parse("abc\n\ndef").unwrap(),
TDocument {
source: TSpan {
data: "abc\n\ndef",
line: 1,
col: 1,
offset: 0
},
blocks: vec![
TBlock::Simple(TSimpleBlock {
inlines: vec![TSpan {
data: "abc",
line: 1,
col: 1,
offset: 0,
},],
source: TSpan {
data: "abc\n",
line: 1,
col: 1,
offset: 0,
}
}),
TBlock::Simple(TSimpleBlock {
inlines: vec![TSpan {
data: "def",
line: 3,
col: 1,
offset: 5,
},],
source: TSpan {
data: "def",
line: 3,
col: 1,
offset: 5,
}
}),
],
}
);
}
// I understand the purpose behind this warning, but
// this module/submodule layout feels preferable in this
// circumstance.
#[allow(clippy::module_inception)]
mod document;
File renamed without changes.
8 changes: 6 additions & 2 deletions src/tests/fixtures/document/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
mod tdocument;
pub(crate) use tdocument::TDocument;
// I understand the purpose behind this warning, but
// this module/submodule layout feels preferable in this
// circumstance.
#[allow(clippy::module_inception)]
mod document;
pub(crate) use document::TDocument;

0 comments on commit a75a436

Please sign in to comment.