-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
implementing preprocessors #532
implementing preprocessors #532
Conversation
src/preprocess/links.rs
Outdated
for section in &mut book.sections { | ||
match *section { | ||
BookItem::Chapter(ref mut ch) => { | ||
let content = ::std::mem::replace(&mut ch.content, String::new()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you need to use mem::replace()
here. You could store the replaced text in a temporary (let replaced = replace_all(&ch.content, base)
) then update ch.content
on the next line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, thanks
@JaimeValdemoros I just had another skim through what you've done so far and it looks pretty good 👍 You may want to add a couple tests for the preprocessor discovery, because I feel like there are a couple edge cases we haven't accounted for. For example, what happens if the user passes in an unknown preprocessor, if you specify a preprocessor twice, or if one of the elements in the The easiest way to write the test is probably to create a bunch of dummy configs (just raw strings), run |
Hi @JaimeValdemoros, this is just a friendly ping to see how you're going with everything 😀 How far through this issue would you say you are? And is there anything I can do to help? |
Hey - sorry about the delay, I've been away and haven't been able to get a
free evening to get it finished. Will have it done tomorrow afternoon if
that's ok.
On 14 Jan 2018 10:34 am, "Michael Bryan" <notifications@github.com> wrote:
Hi @JaimeValdemoros <https://github.com/jaimevaldemoros>, this is just a
friendly ping to see how you're going with everything 😀 How far through
this issue would you say you are? And is there anything I can do to help?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#532 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AH7W0_BHAkA06LRP6Oqx3GL52QNGy6X9ks5tKcoZgaJpZM4RVrx6>
.
|
@Michael-F-Bryan I've made a couple of small changes that felt a bit more sensible:
Is there anything you think is still missing? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I'm really happy with this PR, it looks clean and well thought-out and easily testable 👍
I've made a couple comments/questions, but they're mainly to throw around ideas or ask your opinion.
We may also want to add an integration test which makes sure the preprocessors actually get called.
This could be as simple as creating a dummy Preprocessor
which contains a Rc<AtomicBool>
and will toggle the bool when it gets called. The test then does a build of the dummy book and asserts the bool is now true.
src/book/mod.rs
Outdated
Some(ref p) => p, | ||
// If no preprocessor field is set, default to the LinkPreprocessor. This allows you | ||
// to disable the LinkPreprocessor by setting "preprocess" to an empty list. | ||
None => return Ok(vec![Box::new(LinkPreprocessor::new())]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reckon we may want to pull this bit out into its own default_preprocessors()
function, where we immediately return vec![Box::new(LinkPreprocessor::new())]
. I imagine that'll make things easier to update in the future, and it's immediately obvious what the default preprocessors are.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done - you're right, makes it a bit cleaner
src/book/mod.rs
Outdated
_ => {} | ||
} | ||
for key in preprocess_list { | ||
if key == "links" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are your thoughts on turning this if key == "links" {...} else { bail!() }
into a match statement? Now that preprocessors are easier to work with, I imagine we'll be adding a bunch more in the future.
@@ -149,14 +154,23 @@ impl MDBook { | |||
pub fn build(&self) -> Result<()> { | |||
debug!("[fn]: build"); | |||
|
|||
let mut preprocessed_book = self.book.clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that we're not mutating the original book here and instead preprocessing its clone 👍
src/book/mod.rs
Outdated
@@ -190,6 +204,13 @@ impl MDBook { | |||
self | |||
} | |||
|
|||
/// You can add a new preprocessor by using this method. | |||
/// The only requirement is for your renderer to implement the Preprocessor trait. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doc-comment should probably be reworded in the "imperative" tense to describe what it does. So something like "Register a [Preprocessor
] to be used when rendering the book", with the word "Preprocessor" being a link to the relevant trait so people can see the required methods and what types already implement Preprocessor
.
|
||
pub trait Preprocessor { | ||
fn name(&self) -> &str; | ||
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm tossing up whether to include the Book
as part of the PreprocessorContext
. Do you think the book to be processed should be part of the context or should we keep it as a second argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's helpful to distinguish the fact that you only expect the Book
itself to be mutated, and that the PreprocessorContext
is additional information it can use to help in that mutation. Having a single parameter &mut PreprocessorContext
obscures that slightly and has fewer guarantees about what the Preprocessor
is allowed to do to the context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah that's a good point. I completely forgot the Book
is &mut
while we pass an immutable reference for the PreprocessorContext
.
src/preprocess/mod.rs
Outdated
use std::path::PathBuf; | ||
|
||
pub struct PreprocessorContext { | ||
pub src_dir: PathBuf |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably better for PreprocessorContext
to contain a reference (or clone, if you don't want to deal with lifetimes) to the Config
and the book's root. That way a preprocessor can hook into book.toml
for configuration, and the src_dir
can be discovered by combining config.book.src
and the book root.
So something like this:
pub struct PreprocessorContext {
pub config: Config,
pub root: PathBuf,
// maybe also include the `Book` here... I dunno
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense, given the other fields of the MDBook
shouldn't be needed by the preprocessor. I've made it a clone for now since a borrow would be problematic here:
since we'd have a reference to an inner field of self
in the Context
and additionally trying to take a reference to itself to pass into iter
.
@JaimeValdemoros, I've made a temporary fix (#549) to CI so travis won't fail and erroneously say that this PR is broken. If you want, you can |
535890c
to
90fa1b4
Compare
Thanks @Michael-F-Bryan - this is the first time I've rebased onto a fork so hopefully I've done it right |
tests/testing.rs
Outdated
fn mdbook_runs_preprocessors() { | ||
|
||
lazy_static! { | ||
static ref HAS_RUN: Mutex<bool> = Mutex::new(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to use lazy_static!()
here, an Arc<Mutex<bool>>
should be enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I was trying to have a dummy
option in the Config and have the book add the DummyPreprocessor
to the list when it found the dummy
option, which did require the lazy_static
since the test wasn't constructing it directly. I should've rethought the design when I switched to using with_preprocessor
and building the DummyPreprocessor
directly.
Looks like everything's okay 👍 |
Just to list what I've done since the last checklist:
|
I just had another look through the code and I'm happy with it. I think I'll merge this now and then we can do a follow-up PR to document how preprocessors work in the user guide. Awesome work @JaimeValdemoros, I'm really happy with this! |
…eprocessors implementing preprocessors
…ust-lang#564) * Looks like we forgot to recursively apply replace_all() in rust-lang#532 * Removed some print statements * Made sure we ignore the rendered dummy_book
No description provided.