-
-
Notifications
You must be signed in to change notification settings - Fork 151
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
Refactor html output functions #529
Conversation
Went a little nuts today 😬 |
Run on Wed Feb 5 01:40:02 UTC 2025 |
I was trying to keep the function signature the same (always the same args) for all the |
D'oh! Maybe we should think about an MSRV bumping policy? Right now we're sitting at 1.62.1 maybe forever, whereas some people out there are using N-2 (!).
The advantage that sticks out in my head is they become more amenable to generated (macro) code; we can't inspect the target function signatures at runtime, but if they all share a signature it'd be possible to do some kind of programmatically generated dispatch! |
That's kind of what I was thinking, maybe setting us up for that possibility.
I think you're right. I'm not sure what exactly the policy should be. One part it's nice not to require a version that's too bleeding edge, so that larger organizations don't need to upgrade their rust version as quickly. For example GitLab is still on 1.73. But we also don't want to completely limit ourselves on the ability to use certain features. Maybe it makes sense, for now, to evaluate when we run into something like the |
I've gone ahead and added a commit to bump the MSRV to I could rewrite the code to not use the wdyt? 🤔 |
Hey @kivikakk. So I've made the https://gitlab.com/gitlab-org/ruby/gems/gitlab-glfm-markdown/-/merge_requests/76/diffs?commit_id=eb78621bfe8f47b6a41652c68ed2e95d5cfc0883 is a hacked together way in which it might be used. I made all the So maybe I wouldn't need to make all those public. This relates to the discussion we had on this topic. I admit I posed the question to an AI and it lead me to this
// Original crate
struct OriginalType;
impl OriginalType {
fn method(&self) {
println!("Original method");
}
}
// Your code
trait ExtendedBehavior {
fn method(&self);
}
impl ExtendedBehavior for OriginalType {
fn method(&self) {
println!("Extended method");
}
}
fn main() {
let obj = OriginalType;
obj.method(); // Prints: "Original method"
ExtendedBehavior::method(&obj); // Prints: "Extended method"
} So I basically used a wrapper type in my hacked attempt. |
I'm happy with making one or all the renderer functions public, tbh! But maybe better to just do the main one (so fewer changes are "breaking").
Yes, indeed; same goes for distro package maintainers.
Works for me! :) |
4930706
to
7336534
Compare
Ok, I know better than this - always keep PRs as small as possible. So this just has the refactoring of the render functions. In another PR I will attempt to make |
This allows us to use the `let...else` style syntax
7336534
to
545419e
Compare
@@ -400,7 +400,7 @@ where | |||
plain | |||
} else { | |||
stack.push((node, false, Phase::Post)); | |||
self.format_node(node, true)? | |||
!self.format_node(node, true)? |
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 is an interesting change; can you elaborate on it?
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.
(Also, to be clear, if it's a mistaken change, then the real mistake is that no test failure resulted from it! And if it isn't, we should add a regression test! Either way it's interesting.)
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 sorry, I meant to comment on that change.
All the new render_
functions return a io::Result<bool>
, and return an Ok(true)
when the function is successful. The only render function that can return an Ok(false)
is render_image
. And that false
needs to trigger some additional work, just like it did before.
Originally format_node
was returning Ok(false)
by default, which would set new_plain
in format
.
But with the render functions returning a true
for success, then it made more sense to set a processing_complete
boolean with value of the render functions, and return that. So now we had to negate the return value in the line above.
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 tests do fail pretty spectacularly if the !
is removed.
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, got it; I misread the diff and thought the true
s and false
s hadn't in fact switched positions. I might change those to a binary enum that actually make clear what work they're doing.
You may again wish to consider the approach I outlined in the discussion: #454 (reply in thread); with an exposed |
Yeah that would be great. I haven't done anything with macros in Rust, and didn't quite follow what you were suggesting at the time. |
Will do in a bit, then. |
I have pushed an example of what I mean here: https://github.com/kivikakk/comrak/compare/custom-html-formatter?w=1 See the added test for a sample use. I'm extremely low on cycles and I basically hacked this together in the fastest way possible to demonstrate the idea. There's a lot of cleanup that can be done; I'm exposing a whole bunch of things here which it'd be nicer not to expose. I am 100% sure this can be done cleaner, and I wouldn't want to merge this as-is — it's just to give you an idea of how a macro could help produce the outcome you want. Note how the baseline |
Oh awesome, thanks! I'll look it over in the next few days |
Ok, I see that the basic component of it is generating the I guess one of the benefits of this is that it's inline - there's not another level of function as there would be with the adapter interface. As you said, it would need to be scoped down significantly. Making the entire formatter into a macro somewhat feels like adding a lot of complexity to it. I tend to fall on the side of less meta-programming unless there is a huge win. I did try to integrate it into my crate to test it out, and I couldn't get it to compile, it always had a problem expanding the macro. I was able to very minimal macro working, so I know that using a macro works in general. |
Well, the formatter isn't a macro: it's generated by a macro. Imagine the above code was actually merged as-is into Comrak itself; you could then create your formatter at compile time with This is the kind of thing I'm proposing: it would allow any Comrak user to use the supplied formatter, the same way they currently do, and the code they're actually using would be identical (the macro generates ~identical code as what we have currently in It doesn't need to be "scoped down", I don't think, just cleaned up; I mocked it up as quickly as possible and it's not quite merge-quality, but the general idea is sound. |
But it basically inlines the entire formatter into your own code, right? If that's true, then it seems like whatever dependencies get added to the original formatter, now those need to get included where the custom formatter is defined. As opposed to maybe still using a macro, but making it a small interface into the main formatter. I guess what I'm concerned about is if we make some type of change, such as struct like I may be overthinking it though. |
For example I feel like my custom formatter should only ever need to |
We can use |
Alright, I’m sold, let’s do it! 🚀 |
I'll clean it up then :) |
(It's already looking like 10000x better; moving all the default functions out into |
Refactored the html formatter to handle each node via a separate functions.