-
Notifications
You must be signed in to change notification settings - Fork 433
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
Custom extras #519
Custom extras #519
Conversation
The way this works is by creating subclasses of the `Extra` class. These subclasses will have an order and a name. The name is the same one specified in the `extras` list/dict given to the Markdown init function. The order will be at which point the function will be executed. This is done by attaching the extra to a "Stage", a distinct step in the markdown process (eg: forming paragraphs, processing links... etc). You can set the extra to run before or after the stage. At the moment, extras are automatically registered, activated and executed by the Markdown class. TODO: * More elegant way to register and init extras * Optimise `Stage.mark` * Convert more extras to new class format
Stay up to date with master
Also converted `mermaid` extra as part of this process. As a plus, you no longer need fenced-code-blocks activated to use mermaid. Also added the ability for extras to be triggered before or after another extra
All block extras have now been converted
Still TODO is things like `footnotes` extra. Also need to think of a system for extras to replace parts of the standard syntax
Merge master to pull in trentm#514 for conversion to new Extra format
Also fix tests not running on Windows
At time of commit, Python 3.5 is 2 years 9 months EOL.
Changed in 5478960 so that extras must manually registered by calling the I have also dropped Python 3.5 support since it was throwing errors in the test suite due to some type hints. Python 3.5 was EOL 2 years and 9 months ago so this shouldn't be too much of an issue |
Somehow this slipped through the cracks for me. I'll get it into my queue |
…nto custom-extras Resolve merge conflicts for PR
Thanks for the ping. Markdown2 is just one of three supported markdown engines. Each is configured a little differently with different capabilities. The things I would use this for (recognizing autolinked phrases like issue1) etc are done by changing the text seen by markdown2 so it includes the proper markdown syntax for the links. So I try to do all customization via markdown syntax. I do enable options (e.g. fenced blocks, line breaks on newline) that are supported on all the engines. That said, I do have custom code for markdown and mistune that handle setting the rel Not sure if the customization methods of other engines may be useful feedback. https://github.com/roundup-tracker/roundup/blob/master/roundup/cgi/templating.py#L122-L235 |
Thanks for the heads-up! This looks like a sensible refactor, no complaints really. :) From an API standpoint I'm not sure if having a dedicated |
Apologies for the slow reply, Thanks all for the feedback! @rouilj I took a look at the examples you linked, and tried my hand at implementing a few of them. All worked out pretty well, although a bit more manual work was needed since this library lacks facilities like the The developer experience could be made even better by perhaps implementing a class LinkPostProcessor(Extra, ABC):
order = Stage.after(Stage.LINKS)
def test(self, text):
return True
@abstractmethod
def sub(self, match):
...
def run(self, text):
return re.sub(r'(<a.*?)(?P<href>href="md5-[a-z0-9]{32}")(.*>)', self.sub, text)
class MyExtra(LinkPostProcessor):
name = 'my-extra'
def sub(self, text):
return f'[REMOVED A LINK WITH WITH HREF {match.group("href"}]' I could see such convenience classes being quite useful for quickly creating extras. Would likely implement as and when needed, probably to simplify existing extras that function in similar ways. @mhils The separated |
Allows extras to check exactly when they've been triggered - before or after certain stages
Remove unneeded type hints
Ended up implementing a `ItalicsAndBoldProcessor` ABC extra class for both to piggy-back off of. Works by hashing text that we don't want processed and then doing nothing with the other text. Hashed text is then replaced after I&B has run
Resolve merge conflicts for PR
Resolve merge conflicts for PR
Thanks @Crozzers! 🍰
This sounds reasonable to me - I think an explicit reminder may be a good idea. :) In that case it should probably not have a default implementation, otherwise that defeats that purpose. 😄 We'd have our first real use case for custom extras over in mitmproxy/pdoc#639, so while no hurries please I'm looking forward to this landing at some point. 😃 |
Merge master to resolve PR conflicts
Resolve PR conflicts
Resolve conflicts for PR
@nicholasserra would it be alright to revisit this PR? With all the recent changes to different extras it's getting a bit cumbersome to keep porting them back to this branch. There are also a couple of issues that would benefit from this:
Many thanks |
Sure thing i'll get this on my priority list thank you |
Merge recent changes to `breaks` functionality
This is looking pretty fancy to me. I like it. Anythings better than the free for all we have right now. Still wrapping my head around the numerical aspect of the stages though. What's that getting us that something like an ordered list or dict isn't? I need to read through the code a bit longer to properly grasp what's happening. But overall I think this should be great and we'll land it as-is or with minor tweaks. Just give me a little more time to think on it. |
An ordered list would be simpler and easier and would dodge this flaw in the number system:
The only issue is: if an extra is tied to a particular stage and that stage gets skipped for some reason, do we still want to run that extra? With the numbering system, we can say that anything where [Stage.PREPROCESS, FencedCodeBlocks, Stage.HASH_HTML, FencedCodeBlocks, Stage.LINK_DEFS]
We could instead have ordered lists for each extra. For example: # I've manually filled out the lists here but this would be filled by calling `Stage.before/after`
class Stage:
# tuple containing the `before` and `after` lists respectively. Could be done as a namedtuple
PREPROCESS = ([], [FencedCodeBlocks])
HASH_HTML = ([], [])
LINK_DEFS = ([FencedCodeBlocks], []) This solves the issue of numbers overflowing whilst still allowing us to tie extras to particular stages. The only downside is if an extra sets its order as I'll play around with this and see what happens |
… ordering Lists let us have a theoreticaly infinite number of extras registered, with no overflowing into the next stage
@nicholasserra, I've refactored the ordering system for extras. I've moved most of the ordering logic into the The type of the class MyExtra(Extra):
# run before preprocessing, after HASH_HTML and after the FencedCodeBlocks extra is run
order = [Stage.PREPROCESS], [Stage.HASH_HTML, FencedCodeBlocks] When you call {<Stage.PREPROCESS: 1>: (
[], # before
[<class 'markdown2.Mermaid'>, # after
<class 'markdown2.Wavedrom'>,
<class 'markdown2.FencedCodeBlocks'>]),
<Stage.HASH_HTML: 2>: (
[], # before
[<class 'markdown2.MarkdownInHTML'>]), # after
[...]
<Stage.ITALIC_AND_BOLD: 14>: (
[<class 'markdown2.Underline'>, # before
<class 'markdown2.Strike'>,
<class 'markdown2.MiddleWordEm'>,
<class 'markdown2.CodeFriendly'>],
[<class 'markdown2.Breaks'>, # after
<class 'markdown2.MiddleWordEm'>,
<class 'markdown2.CodeFriendly'>,
<class 'markdown2.TelegramSpoiler'>])} As mentioned previously, the Calling Finally, in the changelog I've put these changes under the most recent header, which is |
@Crozzers This is all looking amazing. I think i'm ready to merge this in. Thank you for all the work on this! I see you have some other open PRs. How would you like to proceed here? Merge this in now, and then fix up the others (if needed), or should I merge those others in and this PR can be rebased? Your call. I think i'll do a release of 2.4.13 before this lands. Then we can move these changes into 2.5.0 as you recommended. I'll just edit the changelog manually when it comes time. |
None of the other PRs are particularly high priority and #568 requires some further investigation so I'd say merge this one first and I can update the others if needed. They're small patches anyway so shouldn't need much doing to them. Thanks for taking the time to review this PR, it ended up as a bit of a monster |
Ok then i'm gonna do a release of the current changes, then merge this, then manually bump us to 2.5.0 after |
This PR closes #382 by adding support for custom extras.
@JeffAbrahamson, @Einenlum and @kjaymiller, you've all expressed interest in this feature, so I'd love to hear any thoughts from you all.
How it works
Extras
Extras must inherit from the new
Extra
class. See theStrike
extra for a basic example of the implementation.Each extra must define the following properties:
name
extras=[]
param, likefenced-code-blocks
.order
run()
test()
fenced-code-blocks
extra might check'```' in text
Extra options
The new extras support passing in extra parameters, like with existing extras. These are passed to the initialiser of the extra via the
extras
dict in theMarkdown
class (see_setup_extras
)Stages
Converting markdown to HTML happens in distinct stages, such as preprocessing, hashing HTML, processing lists, etc. These distinct stages are given numeric IDs in the
Stage
class which denote the order they run in.The
Markdown
class methods that correspond to each stage get decorated with@Stage.mark
. For example:Now, when
preprocess
is called, a wrapper function will find all registered extras that are in the same band as this stage and execute them.Order of execution
As mentioned previously, each stage has its own numeric ID, each 100 apart from the last. Calling
Stage.before/after(Stage.WHATEVER)
will return that stage's ID minus/plus 5. Conescutive calls increment an internal counter and will continue subtracting/adding 5. For example:This means that multiple extras can be registered for the same stage and will be executed in the order in which they were registered.
Known flaws
Stage.before
10 times on the same stage will result in extras overflowing into the next stageStage.after([some stage])
will actually meanStage.before([next stage])
Stage.after([stage], step=1)
to increase the number of calls that can be made before this happensExtra
are auto-detected and registered during_setup_extras
. I intend to change so extras must explicitly register themselvesfootnotes
extra and a few others are tightly integrated and I haven't been able to untangle them as of yetMarkdown
class so there are still shims in place