Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restructure document module #21

Merged
merged 2 commits into from
Dec 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
124 changes: 124 additions & 0 deletions src/tests/document/document.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use pretty_assertions_sorted::assert_eq;

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

#[test]
fn impl_clone() {
// Silly test to mark the #[derive(...)] line as covered.
let doc1 = Document::parse("").unwrap();
let doc2 = doc1.clone();
assert_eq!(doc1, doc2);
}

#[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;
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;
Loading