-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add complete preprocessor example #629
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
Conversation
Oh wow, I'm really pleased to see other people using
Sorry if it feels difficult to use at the moment. We thought we'd start off simple with the preprocessor/alternate renderer architecture and then expand things as we implement things in real-life and get feedback from the community. As such, we're definitely open to suggestions and helping solve any pain points you may be experiencing! |
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 really like the idea behind this example. At less than 100 lines it's simple enough for people to wrap their heads around, yet still quite relevant and something which isn't unreasonable for people to want to do.
I made a couple comments. They're mostly stylistic small things, but overall I'm really happy with this PR 👍
examples/de-emphasize.rs
Outdated
extern crate pulldown_cmark; | ||
extern crate pulldown_cmark_to_cmark; | ||
|
||
// This program removes all forms of emphasis from the markdown of the book. |
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.
Wouldn't this be better suited as a crate-level comment (//! ...
)?
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.
Great idea! I totally forgot these exist!
examples/de-emphasize.rs
Outdated
let mut res: Option<_> = None; | ||
let mut num_removed_items = 0; | ||
book.for_each_mut(|item: &mut BookItem| { | ||
if let &Some(Err(_)) = &res { |
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.
Just seeing this makes me think it'd be nicer to make the Book::sections
field public and let people recursively walk the book's contents. The reason I originally wanted to have a for_each_mut()
method is to prevent accidental iterator invalidation-like issues (e.g. if I add a nested item to some chapter, should we visit that too?) or forgetting to recurse.
Looking at how painful it is to continue on or return early, this approach may be a bit too restrictive... Thoughts?
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.
Indeed! These were my thoughts too. I was wondering too as to why these fields are hidden - they making interacting with the book more difficult on both sides. But on the other hand I am someone who is a sworn enemy of data hiding in case the internal state is fully understood.
My suggestion would be to just make the state of the mdbook public, as there is no 'invalid' state thanks to Rusts type system. There are no special variants that would have to be maintained by methods, for example.
As a sidenote: I noticed that it's impossible to currently get rid of the default renderer, which is an inconvenience for termbook play
for example, as it will always generate html docs as side-effect.
examples/de-emphasize.rs
Outdated
"md-links-to-html-links" | ||
} | ||
|
||
fn run(&self, _ctx: &PreprocessorContext, book: &mut Book) -> ::std::result::Result<(), Error> { |
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.
If you glob import mdbook's errors(use mdbook::errors::*
) you won't need to fully qualify the ::std::result::Result<Error>
here. It's a personal style thing, but I tend to find fully qualified names less readable.
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! Except that I just import Result
as I think spelling it out is preferable if the item count is low.
examples/de-emphasize.rs
Outdated
}); | ||
cmark(events, &mut buf, None) | ||
}); | ||
if let &Some(Ok(_)) = &res { |
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 believe you don't need to borrow res
here if you aren't using the thing inside it (the _
).
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.
Absolutely true! I changed things around to ... something so different this doesn't apply anymore.
examples/de-emphasize.rs
Outdated
} | ||
} | ||
|
||
fn do_it(book: OsString) -> ::std::result::Result<(), Error> { |
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.
Just out of curiosity, what was the reasoning for explicitly take an OsString
here? It's not every day you see OsString
being used outside of an FFI/OS context.
We also load books from the file system, so I usually take a &Path
or even P: Into<PathBuf>
or P: AsRef<Path>
like in File::open()
.
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.
Arguments are natively stored in OsStrings
which have arbitrary encoding. Paths
also assume no encoding, as they are interpreted by the OS.
Thus I generally don't convert program arguments to String
if they should end up in a Path
as this would unnecessarily attempt to decode them as utf-8, which might fail.
examples/de-emphasize.rs
Outdated
chapter.content = buf; | ||
} | ||
} | ||
}); |
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.
Can we add a newline in here so the code is a little less crowded?
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.
The code changed a lot, please re-evaluate. Otherwise, anytime :)!
examples/de-emphasize.rs
Outdated
return; | ||
} | ||
if let BookItem::Chapter(ref mut chapter) = *item { | ||
eprintln!("{}: processing chapter '{}'", self.name(), chapter.name); |
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'd probably pull this closure body out into its own function (e.g. remove_emphasis(ch: &mut Chapter)
). That way the example is easier for people to read and wrap their heads around when they're trying to understand using mdbook
as a library.
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 agree! And pulled out a part of it. Thanks to the shared state and the hackery with the result pulling it out on the highest level makes it even more complicated.
However, I think I found a sweet spot that makes it better actually, and I hope you will feel similarly.
## A complete Example | ||
|
||
The magic happens within the `run(...)` method of the `Preprocessor` trait implementation. |
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 a good idea to add a link to the Preprocessor
trait definition here.
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 agree! For now I chose to link to the version directly, without wildcards.
I didn't mean to emphasize the bad, actually I am really happy with Besides, please have a look once more - I hope to have addressed all the things that came up during the review. |
Oh, and by the way: I love that you like this PR! For me a viable next step would be to improve the Preprocessor-Journey by making chapter access easier, and allowing plugin-programs to be used, too. Ideally, |
When I first had a look at the If you want to propose changes to how chapters are accessed then be more than happy to hear what you propose! I've found shelling out to a user-defined program for rendering a book to be quite a nice way to do things. Your renderer can then pull in the In the long term I'd like pre-processors to be somewhat similar, although I'm stumped on a couple things, for instance some preprocessors will only be relevant when combined with a specific backend, and so on. |
It's a lovely idea! I thought that preprocessor programs would be the same, except that they output the possibly changed book to
To me it seems the lowest-hanging fruit is to return a
To me it's alright to expect the user to configure everything correctly in the |
Drive-by refactoring because the de-emphasize preprocessor was broken
* First version of preprocessor example, with quicli It seems it's not worth it right now. * Remove quicli, just to simplify everything * Finish de-emphasise example * Finish preprocessor example in book * Rename preprocessor type * Apply changes requested in review * Update preprocessor docs with latest code [skip CI]
As I recently implemented my own preprocessor (see termbook), I learned how hard it can be to get started with the current level of documentation, and with the current infrastructure.
In order to achieve what I wanted, I created a crate which allows to serialize pulldown-cmark events back to a string representation that is very close to the original (events are lossy to some extend).
I thought it would be a waste just to keep it for myself and thought I should add a paragraph to the preprocessor chapter of the book and add an example to lower the bar for others to add their own preprocessors.
Please let me know what I should change to make it possible to merge.